diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2245cdd636..bc4a52afdf 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,6 +7,7 @@ variables: isNewEsBranchOrPr: $[or(contains(variables['Build.SourceBranch'], 'newes/'), contains(variables['System.PullRequest.SourceBranch'], 'newes/'))] dependencyCheckCacheKey: $[format('dependency-check-v1-{0:yyyyMM}', pipeline.startTime)] dependencyCheckDataDir: '$(Pipeline.Workspace)/dependency-check-data/v1' + GRADLE_USER_HOME: '$(Pipeline.Workspace)/.gradle' trigger: batch: false @@ -64,17 +65,67 @@ stages: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + - stage: GRADLE_WARMUP + displayName: 'Warm up Gradle cache' + dependsOn: [ ] + condition: + and( + succeeded(), + or( + ne(variables['Build.Reason'], 'Manual'), + and(eq(variables['Build.Reason'], 'Manual'), startsWith('${{ parameters.actionToPerform }}', 'run_all_tests')) + ) + ) + jobs: + - job: WARMUP + displayName: 'Download Gradle and toolchains' + container: eclipse-temurin:17-jdk + timeoutInMinutes: 30 + steps: + - checkout: self + fetchDepth: 1 + clean: false + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT + - bash: | + set +e + apt-get update && apt-get install -y git curl + echo "Warming up Gradle and toolchains..." + ./gradlew --no-daemon -q help + exit 0 + displayName: 'Download Gradle and toolchains (best effort)' + condition: ne(variables.GRADLE_CACHE_HIT, 'true') + env: + GRADLE_USER_HOME: $(GRADLE_USER_HOME) + - stage: OPTIONAL_CHECKS displayName: 'Optional checks' - dependsOn: [ ES_S3_UP ] - condition: and(in(dependencies.ES_S3_UP.result, 'Succeeded', 'Skipped'), ne(variables['Build.Reason'], 'Manual')) + dependsOn: [ ES_S3_UP, GRADLE_WARMUP ] + condition: and(in(dependencies.ES_S3_UP.result, 'Succeeded', 'Skipped'), in(dependencies.GRADLE_WARMUP.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'), ne(variables['Build.Reason'], 'Manual')) jobs: - job: CVE_CHECK + container: eclipse-temurin:17-jdk steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - task: Cache@2 displayName: 'Restore CVE DB from cache' inputs: @@ -105,6 +156,7 @@ stages: var_is_fork: $(System.PullRequest.IsFork) var_oss_index_username: $(OSS_INDEX_USERNAME) var_oss_index_password: $(OSS_INDEX_PASSWORD) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) - task: Cache@2 displayName: 'Save updated CVE DB in cache' inputs: @@ -115,18 +167,35 @@ stages: - stage: REQUIRED_CHECKS displayName: 'Required checks' - dependsOn: [ ES_S3_UP ] - condition: and(in(dependencies.ES_S3_UP.result, 'Succeeded', 'Skipped'), ne(variables['Build.Reason'], 'Manual')) + dependsOn: [ ES_S3_UP, GRADLE_WARMUP ] + condition: + and( + in(dependencies.ES_S3_UP.result, 'Succeeded', 'Skipped'), + in(dependencies.GRADLE_WARMUP.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'), + ne(variables['Build.Reason'], 'Manual') + ) jobs: - job: + container: eclipse-temurin:17-jdk steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | echo "[REQUIRED_CHECKS] executing ROR_TASK = $ROR_TASK" ci/run-pipeline.sh + env: + GRADLE_USER_HOME: $(GRADLE_USER_HOME) strategy: maxParallel: 99 matrix: @@ -139,10 +208,11 @@ stages: - stage: TEST displayName: 'Run all tests' - dependsOn: [ ES_S3_UP ] + dependsOn: [ ES_S3_UP, GRADLE_WARMUP ] condition: and( in(dependencies.ES_S3_UP.result, 'Succeeded', 'Skipped'), + in(dependencies.GRADLE_WARMUP.result, 'Succeeded', 'SucceededWithIssues', 'Skipped'), or( ne(variables['Build.Reason'], 'Manual'), and(eq(variables['Build.Reason'], 'Manual'), startsWith('${{ parameters.actionToPerform }}', 'run_all_tests')) @@ -159,12 +229,22 @@ stages: not(and(eq(variables['Build.Reason'], 'Manual'), eq('${{ parameters.actionToPerform }}', 'run_all_tests_on_windows'))) ) displayName: 'Unit tests' + container: eclipse-temurin:17-jdk timeoutInMinutes: 30 steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | # Translate back env vars to avoid cyclical reference :/ export aws_access_key_id=$var_aws_access_key_id @@ -176,6 +256,7 @@ stages: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) ROR_TASK: core_tests + GRADLE_USER_HOME: $(GRADLE_USER_HOME) - task: PublishTestResults@2 condition: failed() inputs: @@ -197,13 +278,22 @@ stages: and(eq(variables['Build.Reason'], 'Manual'), eq('${{ parameters.actionToPerform }}', 'run_all_tests_on_linux')) ) ) - container: openjdk:22-jdk-slim + container: eclipse-temurin:17-jdk timeoutInMinutes: 120 steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | # Translate back env vars to avoid cyclical reference :/ export aws_access_key_id=$var_aws_access_key_id @@ -214,6 +304,7 @@ stages: env: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) - task: PublishTestResults@2 condition: failed() inputs: @@ -244,13 +335,22 @@ stages: and(eq(variables['Build.Reason'], 'Manual'), eq('${{ parameters.actionToPerform }}', 'run_all_tests_on_linux')) ) ) - container: openjdk:22-jdk-slim + container: eclipse-temurin:17-jdk timeoutInMinutes: 120 steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | # Translate back env vars to avoid cyclical reference :/ export aws_access_key_id=$var_aws_access_key_id @@ -261,6 +361,7 @@ stages: env: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) - task: PublishTestResults@2 condition: failed() inputs: @@ -319,12 +420,22 @@ stages: and(eq(variables['Build.Reason'], 'Manual'), eq('${{ parameters.actionToPerform }}', 'run_all_tests_on_linux')) ) ) + container: eclipse-temurin:17-jdk timeoutInMinutes: 120 steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | # Translate back env vars to avoid cyclical reference :/ export aws_access_key_id=$var_aws_access_key_id @@ -335,6 +446,7 @@ stages: env: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) - task: PublishTestResults@2 condition: failed() inputs: @@ -383,13 +495,22 @@ stages: ne(variables.isMaster, true), not(and(eq(variables['Build.Reason'], 'Manual'), startsWith('${{ parameters.actionToPerform }}', 'run_all_tests'))) ) - container: openjdk:22-jdk-slim + container: eclipse-temurin:17-jdk timeoutInMinutes: 120 steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | # Translate back env vars to avoid cyclical reference :/ export aws_access_key_id=$var_aws_access_key_id @@ -400,6 +521,7 @@ stages: env: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) - task: PublishTestResults@2 condition: failed() inputs: @@ -426,13 +548,22 @@ stages: ne(variables.isMaster, true), not(and(eq(variables['Build.Reason'], 'Manual'), startsWith('${{ parameters.actionToPerform }}', 'run_all_tests'))) ) - container: openjdk:22-jdk-slim + container: eclipse-temurin:17-jdk timeoutInMinutes: 120 steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | # Translate back env vars to avoid cyclical reference :/ export aws_access_key_id=$var_aws_access_key_id @@ -443,6 +574,7 @@ stages: env: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) - task: PublishTestResults@2 condition: failed() inputs: @@ -473,12 +605,22 @@ stages: ne(variables.isMaster, true), not(and(eq(variables['Build.Reason'], 'Manual'), startsWith('${{ parameters.actionToPerform }}', 'run_all_tests'))) ) + container: eclipse-temurin:17-jdk timeoutInMinutes: 120 steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | # Translate back env vars to avoid cyclical reference :/ export aws_access_key_id=$var_aws_access_key_id @@ -489,6 +631,7 @@ stages: env: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) - task: PublishTestResults@2 condition: failed() inputs: @@ -778,12 +921,21 @@ stages: ) jobs: - job: + container: eclipse-temurin:17-jdk timeoutInMinutes: 180 steps: - checkout: self fetchDepth: 1 clean: false - + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | set -e @@ -796,6 +948,7 @@ stages: env: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) strategy: maxParallel: 99 @@ -849,13 +1002,22 @@ stages: ) jobs: - job: + container: eclipse-temurin:17-jdk timeoutInMinutes: 600 steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true - + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | set -e @@ -869,6 +1031,7 @@ stages: env: var_aws_access_key_id: $(aws_access_key_id) var_aws_secret_access_key: $(aws_secret_access_key) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) strategy: maxParallel: 99 @@ -899,6 +1062,7 @@ stages: ) jobs: - job: + container: eclipse-temurin:17-jdk timeoutInMinutes: 180 steps: - checkout: self @@ -906,12 +1070,20 @@ stages: clean: false persistCredentials: true timeoutInMinutes: 180 - + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT - script: | set -e echo ">>>> Installing dependencies with apt-get" - sudo apt-get update && sudo apt-get install -y git file + apt-get update && apt-get install -y git file git status && echo ">>> Git installed correctly!" # Translate back env vars to avoid cyclical reference :/ @@ -931,6 +1103,7 @@ stages: var_aws_secret_access_key: $(aws_secret_access_key) var_docker_registry_user: $(DOCKER_REGISTRY_USER) var_docker_registry_password: $(DOCKER_REGISTRY_PASSWORD) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) strategy: maxParallel: 99 @@ -961,12 +1134,21 @@ stages: ) jobs: - job: + container: eclipse-temurin:17-jdk steps: - checkout: self fetchDepth: 1 clean: false persistCredentials: true - + - task: Cache@2 + displayName: 'Restore Gradle cache' + inputs: + key: 'gradle | "$(Agent.OS)" | **/gradle/wrapper/gradle-wrapper.properties | **/settings.gradle* | **/build.gradle* | **/gradle.properties' + restoreKeys: | + gradle | "$(Agent.OS)" + gradle + path: $(GRADLE_USER_HOME) + cacheHitVar: GRADLE_CACHE_HIT # Populate the global variable mvn_status for later - script: | PLUGIN_VER=$(awk -F= '$1=="pluginVersion" {print $2}' gradle.properties) @@ -1013,6 +1195,7 @@ stages: VAR_MAVEN_STAGING_PROFILE_ID: $(MAVEN_STAGING_PROFILE_ID) VAR_GPG_PASSPHRASE: $(GPG_PASSPHRASE) VAR_GPG_KEY_ID: $(GPG_KEY_ID) + GRADLE_USER_HOME: $(GRADLE_USER_HOME) condition: eq(404, variables.mvn_status) - stage: PRE_BUILDS_DOCKER_IMAGE_PUBLISHING diff --git a/build-base/src/main/groovy/readonlyrest.plugin-common-conventions.gradle b/build-base/src/main/groovy/readonlyrest.plugin-common-conventions.gradle index 51248c52d9..a048f1b764 100644 --- a/build-base/src/main/groovy/readonlyrest.plugin-common-conventions.gradle +++ b/build-base/src/main/groovy/readonlyrest.plugin-common-conventions.gradle @@ -84,7 +84,7 @@ tasks.withType(ScalaCompile).configureEach { tasks.withType(Zip).configureEach { task -> task.doLast { - ant.checksum file: it.archivePath, algorithm: 'sha1' + ant.checksum file: task.archiveFile.get().asFile, algorithm: 'sha1' } } @@ -102,6 +102,7 @@ tasks.register('generateVersionsFile') { tasks.register('toJar', Jar) { dependsOn generateVersionsFile duplicatesStrategy = DuplicatesStrategy.EXCLUDE + archiveBaseName = pluginName from sourceSets.main.getOutput() } diff --git a/ci/run-pipeline.sh b/ci/run-pipeline.sh index 0144a03ab9..5bb9174547 100755 --- a/ci/run-pipeline.sh +++ b/ci/run-pipeline.sh @@ -4,43 +4,32 @@ source "$(dirname "$0")/ci-lib.sh" trap 'echo "Termination signal received. Exiting..."; exit 1' SIGTERM SIGINT -echo ">>> ($0) RUNNING CONTINUOUS INTEGRATION" - -export TRAVIS_BRANCH=$(git symbolic-ref --short -q HEAD) - -if [ "$BUILD_SOURCEBRANCHNAME" ]; then - export TRAVIS=true - export TRAVIS_BRANCH=$BUILD_SOURCEBRANCHNAME -fi -echo ">> FOUND BUILD PARAMETERS: task? $ROR_TASK; is CI? $TRAVIS; branch? $TRAVIS_BRANCH" +echo ">>> ($0) RUNNING CONTINUOUS INTEGRATION; task? $ROR_TASK" # Log file friendly Gradle output export TERM=dumb -# Adaptation for Azure -([ ! -z $BUILD_BUILDNUMBER ] || [ "$TRAVIS" ]) && TRAVIS="true" - -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "license_check" ]]; then +if [[ $ROR_TASK == "license_check" ]]; then echo ">>> Check all license headers are in place" ./gradlew --no-daemon license fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "cve_check" ]]; then +if [[ $ROR_TASK == "cve_check" ]]; then echo ">>> Running CVE checks.." ./gradlew --no-daemon dependencyCheckAnalyze fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "compile_codebase_check" ]]; then +if [[ $ROR_TASK == "compile_codebase_check" ]]; then echo ">>> Running compile codebase.." ./gradlew --no-daemon classes fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "audit_build_check" ]]; then +if [[ $ROR_TASK == "audit_build_check" ]]; then echo ">>> Running audit module cross build.." ./gradlew --no-daemon --stacktrace audit:crossBuildAssemble fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "core_tests" ]]; then +if [[ $ROR_TASK == "core_tests" ]]; then echo ">>> Running unit tests.." ./gradlew --no-daemon --stacktrace core:test audit:test fi @@ -53,147 +42,142 @@ run_integration_tests() { ES_MODULE=$1 - echo ">>> $ES_MODULE => Running testcontainers.." + echo ">>> $ES_MODULE => Running integration tests.." ./gradlew --no-daemon ror-tools:test integration-tests:test "-PesModule=$ES_MODULE" || (find . | grep hs_err | xargs cat && exit 1) } -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es92x" ]]; then +if [[ $ROR_TASK == "integration_es92x" ]]; then run_integration_tests "es92x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es91x" ]]; then +if [[ $ROR_TASK == "integration_es91x" ]]; then run_integration_tests "es91x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es90x" ]]; then +if [[ $ROR_TASK == "integration_es90x" ]]; then run_integration_tests "es90x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es818x" ]]; then +if [[ $ROR_TASK == "integration_es818x" ]]; then run_integration_tests "es818x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es816x" ]]; then +if [[ $ROR_TASK == "integration_es816x" ]]; then run_integration_tests "es816x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es815x" ]]; then +if [[ $ROR_TASK == "integration_es815x" ]]; then run_integration_tests "es815x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es814x" ]]; then +if [[ $ROR_TASK == "integration_es814x" ]]; then run_integration_tests "es814x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es813x" ]]; then +if [[ $ROR_TASK == "integration_es813x" ]]; then run_integration_tests "es813x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es812x" ]]; then +if [[ $ROR_TASK == "integration_es812x" ]]; then run_integration_tests "es812x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es811x" ]]; then +if [[ $ROR_TASK == "integration_es811x" ]]; then run_integration_tests "es811x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es810x" ]]; then +if [[ $ROR_TASK == "integration_es810x" ]]; then run_integration_tests "es810x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es89x" ]]; then +if [[ $ROR_TASK == "integration_es89x" ]]; then run_integration_tests "es89x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es88x" ]]; then +if [[ $ROR_TASK == "integration_es88x" ]]; then run_integration_tests "es88x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es87x" ]]; then +if [[ $ROR_TASK == "integration_es87x" ]]; then run_integration_tests "es87x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es85x" ]]; then +if [[ $ROR_TASK == "integration_es85x" ]]; then run_integration_tests "es85x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es84x" ]]; then +if [[ $ROR_TASK == "integration_es84x" ]]; then run_integration_tests "es84x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es83x" ]]; then +if [[ $ROR_TASK == "integration_es83x" ]]; then run_integration_tests "es83x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es82x" ]]; then +if [[ $ROR_TASK == "integration_es82x" ]]; then run_integration_tests "es82x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es81x" ]]; then +if [[ $ROR_TASK == "integration_es81x" ]]; then run_integration_tests "es81x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es80x" ]]; then +if [[ $ROR_TASK == "integration_es80x" ]]; then run_integration_tests "es80x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es717x" ]]; then +if [[ $ROR_TASK == "integration_es717x" ]]; then run_integration_tests "es717x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es716x" ]]; then +if [[ $ROR_TASK == "integration_es716x" ]]; then run_integration_tests "es716x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es714x" ]]; then +if [[ $ROR_TASK == "integration_es714x" ]]; then run_integration_tests "es714x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es711x" ]]; then +if [[ $ROR_TASK == "integration_es711x" ]]; then run_integration_tests "es711x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es710x" ]]; then +if [[ $ROR_TASK == "integration_es710x" ]]; then run_integration_tests "es710x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es79x" ]]; then +if [[ $ROR_TASK == "integration_es79x" ]]; then run_integration_tests "es79x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es78x" ]]; then +if [[ $ROR_TASK == "integration_es78x" ]]; then run_integration_tests "es78x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es77x" ]]; then +if [[ $ROR_TASK == "integration_es77x" ]]; then run_integration_tests "es77x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es74x" ]]; then +if [[ $ROR_TASK == "integration_es74x" ]]; then run_integration_tests "es74x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es73x" ]]; then +if [[ $ROR_TASK == "integration_es73x" ]]; then run_integration_tests "es73x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es72x" ]]; then +if [[ $ROR_TASK == "integration_es72x" ]]; then run_integration_tests "es72x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es70x" ]]; then +if [[ $ROR_TASK == "integration_es70x" ]]; then run_integration_tests "es70x" fi -if [[ -z $TRAVIS ]] || [[ $ROR_TASK == "integration_es67x" ]]; then +if [[ $ROR_TASK == "integration_es67x" ]]; then run_integration_tests "es67x" fi -if [[ $TRAVIS_PULL_REQUEST == "true" ]] && [[ $TRAVIS_BRANCH != "master" ]]; then - echo ">>> won't try to create builds because this is a PR" - exit 0 -fi - build_ror_plugins() { if [ "$#" -ne 1 ]; then echo "What ES versions should I build plugins for?" @@ -220,19 +204,19 @@ build_ror_plugin() { ./gradlew buildRorPlugin "-PesVersion=$ROR_VERSION" + implicit val auditEnvironmentContext: AuditEnvironmentContext = new AuditEnvironmentContextBasedOnEsNodeSettings(settings.esNodeSettings) logger.info(s"The audit is enabled with the given outputs: [${auditSinks.toList.show}]") Some(new AuditingTool(auditSinks)) case None => diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/Block.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/Block.scala index 3ee8db3b07..46a426992b 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/Block.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/Block.scala @@ -31,8 +31,8 @@ import tech.beshu.ror.accesscontrol.blocks.users.LocalUsersContext.LocalUsersSup import tech.beshu.ror.accesscontrol.blocks.variables.runtime.VariableContext.VariableUsage import tech.beshu.ror.accesscontrol.factory.BlockValidator import tech.beshu.ror.accesscontrol.factory.BlockValidator.BlockValidationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.BlocksLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.BlocksLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.accesscontrol.request.RequestContext import tech.beshu.ror.implicits.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/ImpersonationWarning.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/ImpersonationWarning.scala index b3bacf1c5c..f80e5d78e1 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/ImpersonationWarning.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/ImpersonationWarning.scala @@ -164,7 +164,7 @@ object ImpersonationWarning { Some(ImpersonationWarning( block = blockName, ruleName = rule.name, - message = nes("The rule contains fully hashed username and password. It doesn't support impersonation in this configuration"), + message = nes("The rule contains fully hashed username and password. It doesn't support impersonation in this use case."), hint = s"You can use second version of the rule and use not hashed username. Like that: `${rule.name.show}: USER_NAME:hash(PASSWORD)" )) case _: HashedCredentials.HashedOnlyPassword => diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/base/BaseBasicAuthAuthenticationRule.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/base/BaseBasicAuthAuthenticationRule.scala index 60da0a7bd1..f5328c0f55 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/base/BaseBasicAuthAuthenticationRule.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/auth/base/BaseBasicAuthAuthenticationRule.scala @@ -42,7 +42,7 @@ private [auth] abstract class BaseBasicAuthAuthenticationRule implicit val requestId: RequestId = requestContext.id.toRequestId requestContext.basicAuth.map(_.credentials) match { case Some(credentials) => - logger.debug(s"[${requestId.show}] Attempting Login as: ${credentials.user.show}") + logger.debug(s"[${requestId.show}] Attempting authenticate as: ${credentials.user.show}") authenticateUsing(credentials) .map { case true => Fulfilled(blockContext.withUserMetadata(_.withLoggedUser(DirectlyLoggedUser(credentials.user)))) diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/BaseKibanaRule.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/BaseKibanaRule.scala index 89cc6d44e7..e28d22a4f7 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/BaseKibanaRule.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/BaseKibanaRule.scala @@ -23,7 +23,6 @@ import tech.beshu.ror.accesscontrol.blocks.rules.Rule import tech.beshu.ror.accesscontrol.blocks.rules.Rule.RegularRule import tech.beshu.ror.accesscontrol.blocks.rules.kibana.BaseKibanaRule.* import tech.beshu.ror.accesscontrol.blocks.rules.kibana.KibanaActionMatchers.* -import tech.beshu.ror.accesscontrol.domain.ClusterIndexName.Local.devNullKibana import tech.beshu.ror.accesscontrol.domain.KibanaAccess.* import tech.beshu.ror.accesscontrol.domain.* import tech.beshu.ror.accesscontrol.domain.KibanaIndexName.* @@ -254,7 +253,7 @@ abstract class BaseKibanaRule(val settings: Settings) object BaseKibanaRule { abstract class Settings(val access: KibanaAccess, - val rorIndex: RorConfigurationIndex) + val rorIndex: RorSettingsIndex) type ProcessingContext = ReaderT[Id, (RequestContext, KibanaIndexName), Boolean] object ProcessingContext { diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/KibanaAccessRule.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/KibanaAccessRule.scala index b15703af41..b0cb5932f9 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/KibanaAccessRule.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/KibanaAccessRule.scala @@ -50,7 +50,7 @@ class KibanaAccessRule(override val settings: Settings) } private def kibanaIndexFrom(blockContext: BlockContext): KibanaIndexName = { - blockContext.userMetadata.kibanaIndex.getOrElse(ClusterIndexName.Local.kibanaDefault) + blockContext.userMetadata.kibanaIndex.getOrElse(KibanaIndexName.default) } } @@ -61,6 +61,6 @@ object KibanaAccessRule { } final case class Settings(override val access: KibanaAccess, - override val rorIndex: RorConfigurationIndex) + override val rorIndex: RorSettingsIndex) extends BaseKibanaRule.Settings(access, rorIndex) } diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/KibanaUserDataRule.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/KibanaUserDataRule.scala index 676a129833..2dd0c51a06 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/KibanaUserDataRule.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/blocks/rules/kibana/KibanaUserDataRule.scala @@ -133,6 +133,6 @@ object KibanaUserDataRule { appsToHide: Set[KibanaApp], allowedApiPaths: Set[KibanaAllowedApiPath], metadata: Option[ResolvableJsonRepresentation], - override val rorIndex: RorConfigurationIndex) + override val rorIndex: RorSettingsIndex) extends BaseKibanaRule.Settings(access, rorIndex) } \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/elasticsearch.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/elasticsearch.scala index f10ec24887..bbfa8ece6c 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/elasticsearch.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/elasticsearch.scala @@ -53,11 +53,11 @@ object Action { abstract sealed class AdminRorAction(override val value: String) extends RorAction(value) case object RorUserMetadataAction extends RoRorAction("cluster:internal_ror/user_metadata/get") - case object RorConfigAction extends AdminRorAction("cluster:internal_ror/config/manage") - case object RorTestConfigAction extends AdminRorAction("cluster:internal_ror/testconfig/manage") + case object RorMainSettingsAction extends AdminRorAction("cluster:internal_ror/config/manage") + case object RorTestSettingsAction extends AdminRorAction("cluster:internal_ror/testconfig/manage") case object RorAuthMockAction extends AdminRorAction("cluster:internal_ror/authmock/manage") case object RorAuditEventAction extends RwRorAction("cluster:internal_ror/audit_event/put") - case object RorOldConfigAction extends AdminRorAction("cluster:internal_ror/config/refreshsettings") + case object RorRefreshSettingsAction extends AdminRorAction("cluster:internal_ror/config/refreshsettings") def fromString(value: String): Option[Action] = { rorActionFrom(value) @@ -73,21 +73,21 @@ object Action { private def rorActionFrom(value: String): Option[RorAction] = value match { case RorUserMetadataAction.`value` => RorUserMetadataAction.some - case RorConfigAction.`value` => RorConfigAction.some - case RorTestConfigAction.`value` => RorTestConfigAction.some + case RorMainSettingsAction.`value` => RorMainSettingsAction.some + case RorTestSettingsAction.`value` => RorTestSettingsAction.some case RorAuthMockAction.`value` => RorAuthMockAction.some case RorAuditEventAction.`value` => RorAuditEventAction.some - case RorOldConfigAction.`value` => RorOldConfigAction.some + case RorRefreshSettingsAction.`value` => RorRefreshSettingsAction.some case _ => None } private val rorActionByOutdatedName: Map[String, RorAction] = Map( "cluster:ror/user_metadata/get" -> RorUserMetadataAction, - "cluster:ror/config/manage" -> RorConfigAction, - "cluster:ror/testconfig/manage" -> RorTestConfigAction, + "cluster:ror/config/manage" -> RorMainSettingsAction, + "cluster:ror/testconfig/manage" -> RorTestSettingsAction, "cluster:ror/authmock/manage" -> RorAuthMockAction, "cluster:ror/audit_event/put" -> RorAuditEventAction, - "cluster:ror/config/refreshsettings" -> RorOldConfigAction + "cluster:ror/config/refreshsettings" -> RorRefreshSettingsAction ) private def patternMatchingOutdatedRorActionName(possiblePattern: String): Option[EsAction] = { diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/indices.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/indices.scala index 0b1d116800..a62e2e9534 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/indices.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/indices.scala @@ -22,6 +22,8 @@ import cats.implicits.* import com.github.benmanes.caffeine.cache.{Cache, Caffeine} import eu.timepit.refined.auto.* import eu.timepit.refined.types.string.NonEmptyString +import better.files.* +import tech.beshu.ror.accesscontrol.domain.ClusterIndexName.Local import tech.beshu.ror.accesscontrol.domain.ClusterIndexName.Remote.ClusterName import tech.beshu.ror.accesscontrol.matchers.PatternsMatcher import tech.beshu.ror.accesscontrol.matchers.PatternsMatcher.Matchable @@ -74,6 +76,9 @@ object IndexName { final case class KibanaIndexName(underlying: ClusterIndexName.Local) object KibanaIndexName { + val devNullKibana: KibanaIndexName = KibanaIndexName(Local(IndexName.Full(nes(".kibana-devnull")))) + val default: KibanaIndexName = KibanaIndexName(Local(IndexName.Full(nes(".kibana")))) + private val kibanaIndicesRegexesCache: Cache[KibanaIndexName, Vector[Regex]] = Caffeine.newBuilder() .executor(global) @@ -184,7 +189,7 @@ object ClusterIndexName { object Local { val wildcard: ClusterIndexName.Local = Local(IndexName.wildcard) - val devNullKibana: KibanaIndexName = KibanaIndexName(Local(IndexName.Full(nes(".kibana-devnull")))) + val kibanaDefault: KibanaIndexName = KibanaIndexName(Local(IndexName.Full(nes(".kibana")))) def fromString(value: String): Option[ClusterIndexName.Local] = { @@ -523,7 +528,3 @@ object IndexAttribute { case object Opened extends IndexAttribute case object Closed extends IndexAttribute } - -final case class RorConfigurationIndex(index: IndexName.Full) extends AnyVal { - def toLocal: ClusterIndexName.Local = ClusterIndexName.Local(index) -} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/settings.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/settings.scala new file mode 100644 index 0000000000..a69efb7fa9 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/domain/settings.scala @@ -0,0 +1,38 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.accesscontrol.domain + +import better.files.File +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.utils.RefinedUtils.nes + +final case class RorSettingsIndex(index: IndexName.Full) extends AnyVal { + def toLocal: ClusterIndexName.Local = ClusterIndexName.Local(index) +} +object RorSettingsIndex { + val default: RorSettingsIndex = RorSettingsIndex(IndexName.Full(nes(".readonlyrest"))) +} + +final case class RorSettingsFile(file: File) extends AnyVal +object RorSettingsFile { + def default(esEnv: EsEnv): RorSettingsFile = RorSettingsFile(esEnv.configDir / "readonlyrest.yml") +} + +final case class EsConfigFile(file: File) extends AnyVal +object EsConfigFile { + def default(esEnv: EsEnv): EsConfigFile = EsConfigFile(esEnv.configDir / "elasticsearch.yml") +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/CoreFactory.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/CoreFactory.scala new file mode 100644 index 0000000000..39a096ac50 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/CoreFactory.scala @@ -0,0 +1,560 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.accesscontrol.factory + +import cats.data.{NonEmptyList, State, Validated} +import cats.kernel.Monoid +import io.circe.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.* +import tech.beshu.ror.accesscontrol.EnabledAccessControlList.AccessControlListStaticContext +import tech.beshu.ror.accesscontrol.audit.{AuditingTool, LoggingContext} +import tech.beshu.ror.accesscontrol.blocks.Block.{RuleDefinition, Verbosity} +import tech.beshu.ror.accesscontrol.blocks.ImpersonationWarning.ImpersonationWarningSupport +import tech.beshu.ror.accesscontrol.blocks.definitions.UserDef.Mode +import tech.beshu.ror.accesscontrol.blocks.definitions.UserDef.Mode.WithGroupsMapping.Auth +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider +import tech.beshu.ror.accesscontrol.blocks.definitions.{ImpersonatorDef, UserDef} +import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider +import tech.beshu.ror.accesscontrol.blocks.rules.Rule +import tech.beshu.ror.accesscontrol.blocks.rules.Rule.AuthenticationRule.EligibleUsersSupport +import tech.beshu.ror.accesscontrol.blocks.users.LocalUsersContext.{LocalUsersSupport, localUsersMonoid} +import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariableCreator +import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler +import tech.beshu.ror.accesscontrol.blocks.{Block, ImpersonationWarning} +import tech.beshu.ror.accesscontrol.domain.* +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.* +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.* +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RorDependencies.ImpersonationWarningsReader +import tech.beshu.ror.accesscontrol.factory.decoders.definitions.* +import tech.beshu.ror.accesscontrol.factory.decoders.ruleDecoders.ruleDecoderBy +import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleDecoder +import tech.beshu.ror.accesscontrol.factory.decoders.{AuditingSettingsDecoder, GlobalStaticSettingsDecoder} +import tech.beshu.ror.accesscontrol.utils.* +import tech.beshu.ror.accesscontrol.utils.CirceOps.* +import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers.FieldListResult.{FieldListValue, NoField} +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.ror.RawRorSettings +import tech.beshu.ror.syntax.* +import tech.beshu.ror.utils.ScalaOps.* +import tech.beshu.ror.utils.yaml.YamlOps + +final case class Core(accessControl: AccessControlList, + dependencies: RorDependencies, + auditingSettings: Option[AuditingTool.AuditSettings]) + +trait CoreFactory { + def createCoreFrom(rorSettings: RawRorSettings, + rorSettingsIndex: RorSettingsIndex, + httpClientFactory: HttpClientsFactory, + ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, + mocksProvider: MocksProvider): Task[Either[NonEmptyList[CoreCreationError], Core]] +} + +class RawRorSettingsBasedCoreFactory(esEnv: EsEnv) + (implicit systemContext: SystemContext) + extends CoreFactory with Logging { + + override def createCoreFrom(rorSettings: RawRorSettings, + rorSettingsIndex: RorSettingsIndex, + httpClientFactory: HttpClientsFactory, + ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, + mocksProvider: MocksProvider): Task[Either[NonEmptyList[CoreCreationError], Core]] = { + rorSettings.settingsJson \\ Attributes.rorSectionName match { + case Nil => createCoreFromRorSection( + rorSettings.settingsJson, + rorSettingsIndex, + httpClientFactory, + ldapConnectionPoolProvider, + mocksProvider + ) + case rorSection :: Nil => createCoreFromRorSection( + rorSection, + rorSettingsIndex, + httpClientFactory, + ldapConnectionPoolProvider, + mocksProvider + ) + case _ => Task.now(Left(NonEmptyList.one(GeneralReadonlyrestSettingsError(Message(s"Malformed settings"))))) + } + } + + private def createCoreFromRorSection(rorSection: Json, + rorSettingsIndex: RorSettingsIndex, + httpClientFactory: HttpClientsFactory, + ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, + mocksProvider: MocksProvider) = { + val resolver = new JsonStaticVariablesResolver( + systemContext.envVarsProvider, + TransformationCompiler.withoutAliases(systemContext.variablesFunctions), + ) + resolver.resolve(rorSection) match { + case Right(resolvedRorSection) => + createFrom(resolvedRorSection, rorSettingsIndex, httpClientFactory, ldapConnectionPoolProvider, mocksProvider).map { + case Right(settings) => + Right(settings) + case Left(failure) => + Left(NonEmptyList.one(failure.aclCreationError.getOrElse(GeneralReadonlyrestSettingsError(Message(s"Malformed settings"))))) + } + case Left(errors) => + Task.now(Left(errors.map(e => GeneralReadonlyrestSettingsError(Message(e.msg))))) + } + } + + private def createFrom(settingsJson: Json, + settingsIndex: RorSettingsIndex, + httpClientFactory: HttpClientsFactory, + ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, + mocksProvider: MocksProvider) = { + val decoder = for { + enabled <- AsyncDecoderCreator.from(coreEnabilityDecoder) + core <- + if (!enabled) { + AsyncDecoderCreator.from(Decoder.const(Core(DisabledAccessControlList, RorDependencies.noOp, None))) + } else { + for { + globalSettings <- AsyncDecoderCreator.from(GlobalStaticSettingsDecoder.instance(settingsIndex)) + core <- coreDecoder(httpClientFactory, ldapConnectionPoolProvider, globalSettings, mocksProvider) + } yield core + } + } yield core + + decoder(HCursor.fromJson(settingsJson)) + } + + private def coreEnabilityDecoder: Decoder[Boolean] = { + Decoder.instance { c => + for { + enabled <- c.downField("enable").as[Option[Boolean]] + } yield enabled.getOrElse(true) + } + } + + import RawRorSettingsBasedCoreFactory.* + + private def rulesNelDecoder(definitions: DefinitionsPack, + globalSettings: GlobalSettings, + mocksProvider: MocksProvider): Decoder[NonEmptyList[RuleDefinition[Rule]]] = Decoder.instance { c => + val init = State.pure[ACursor, Validated[List[String], Decoder.Result[List[RuleDefinition[Rule]]]]](Validated.Valid(Right(List.empty))) + + val (_, result) = c.keys.toList.flatten // at the moment kibana_index must be defined before kibana_access + .foldLeft(init) { case (collectedRuleResults, currentRuleName) => + for { + last <- collectedRuleResults + current <- decodeRuleInCursorContext(currentRuleName, definitions, globalSettings, mocksProvider).map { + case RuleDecodingResult.Result(value) => Validated.Valid(value.map(_ :: Nil)) + case RuleDecodingResult.UnknownRule => Validated.Invalid(currentRuleName :: Nil) + case RuleDecodingResult.Skipped => Validated.Valid(Right(List.empty)) + } + } yield Monoid.combine(last, current) + } + .run(c) + .value + + result match { + case Validated.Valid(r) => + r.flatMap { a => + NonEmptyList.fromList(a) match { + case Some(rules) => + Right(rules) + case None => + Left(DecodingFailureOps.fromError(RulesLevelCreationError(Message(s"No rules defined in block")))) + } + } + case Validated.Invalid(unknownRules) => + Left(DecodingFailureOps.fromError(RulesLevelCreationError(Message(s"Unknown rules: ${unknownRules.show}")))) + } + } + + private def decodeRuleInCursorContext(name: String, + definitions: DefinitionsPack, + globalSettings: GlobalSettings, + mocksProvider: MocksProvider): State[ACursor, RuleDecodingResult] = { + State(cursor => { + if (!cursor.keys.toList.flatten.contains(name)) { + (cursor, RuleDecodingResult.Skipped) + } else { + ruleDecoderBy(Rule.Name(name), definitions, globalSettings, mocksProvider) match { + case Some(decoder) => + decoder.tryDecode(cursor) match { + case Right(RuleDecoder.Result(rule, unconsumedCursor)) => + (unconsumedCursor, RuleDecodingResult.Result(Right(rule))) + case Left(failure) => + (cursor, RuleDecodingResult.Result(Left(failure))) + } + case None => + (cursor, RuleDecodingResult.UnknownRule) + } + } + }) + } + + private def blockDecoder(definitions: DefinitionsPack, + globalSettings: GlobalSettings, + mocksProvider: MocksProvider) + (implicit loggingContext: LoggingContext): Decoder[BlockDecodingResult] = { + implicit val nameDecoder: Decoder[Block.Name] = DecoderHelpers.decodeStringLike.map(Block.Name.apply) + implicit val policyDecoder: Decoder[Block.Policy] = this.policyDecoder + implicit val verbosityDecoder: Decoder[Verbosity] = + Decoder + .decodeString + .toSyncDecoder + .emapE[Verbosity] { + case "info" => Right(Verbosity.Info) + case "error" => Right(Verbosity.Error) + case unknown => Left(BlocksLevelCreationError(Message(s"Unknown verbosity value: ${unknown.show}. Supported types: 'info'(default), 'error'."))) + } + .decoder + implicit val blockAuditDecoder: Decoder[Block.Audit] = + Decoder.instance { c => + for { + enabled <- c.downField("enabled").as[Boolean] + } yield { + if (enabled) Block.Audit.Enabled else Block.Audit.Disabled + } + } + Decoder + .instance { c => + val result = for { + name <- c.downField(Attributes.Block.name).as[Block.Name] + policy <- c.downField(Attributes.Block.policy).as[Option[Block.Policy]] + verbosity <- c.downField(Attributes.Block.verbosity).as[Option[Block.Verbosity]] + audit <- c.downField(Attributes.Block.audit).as[Option[Block.Audit]] + rules <- rulesNelDecoder(definitions, globalSettings, mocksProvider) + .toSyncDecoder + .decoder + .tryDecode(c.withFocus( + _.mapObject(_ + .remove(Attributes.Block.name) + .remove(Attributes.Block.policy) + .remove(Attributes.Block.verbosity) + .remove(Attributes.Block.audit) + ) + )) + block <- Block.createFrom(name, policy, verbosity, audit, rules).left.map(DecodingFailureOps.fromError(_)) + } yield BlockDecodingResult( + block = block, + localUsers = rules.map(localUsersForRule).combineAll, + impersonationWarnings = new BlockImpersonationWarningsReader(block.name, rules) + ) + result.left.map(_.overrideDefaultErrorWith(BlocksLevelCreationError(MalformedValue(c.value)))) + } + } + + private val obfuscatedHeadersAsyncDecoder: Decoder[Set[Header.Name]] = { + import tech.beshu.ror.accesscontrol.factory.decoders.common.headerName + Decoder.instance(_.downField("obfuscated_headers").as[Option[Set[Header.Name]]]) + .map(_.getOrElse(Set(Header.Name.authorization))) + } + + private val policyDecoder: Decoder[Block.Policy] = { + def unknownTypeError(unknownType: String) = + BlocksLevelCreationError(Message( + s"Unknown block policy type: ${unknownType.show}. Supported types: 'allow'(default), 'forbid'." + )) + + val simplePolicyDecoder = { + Decoder + .decodeString + .toSyncDecoder + .emapE[Block.Policy] { + case "allow" => Right(Block.Policy.Allow) + case "forbid" => Right(Block.Policy.Forbid()) + case unknown => Left(unknownTypeError(unknown)) + } + .decoder + } + + val extendedPolicyDecoder = { + Decoder + .instance { c => + for { + policyType <- c.downFieldAs[String]("policy") + policy <- policyType match { + case "allow" => Right(Block.Policy.Allow) + case "forbid" => c.downFieldAs[Option[String]]("response_message").map(Block.Policy.Forbid.apply) + case unknown => Left(DecodingFailureOps.fromError(unknownTypeError(unknown))) + } + } yield policy + } + .toSyncDecoder + .decoder + } + + Decoder.instance { c => + c.focus match { + case Some(f) if f.isString => simplePolicyDecoder(c) + case Some(f) if f.isObject => extendedPolicyDecoder(c) + case Some(_) | None => Left(DecodingFailure("Malformed block policy type", c.history)) + } + } + } + + private def localUsersForRule[R <: Rule](rule: RuleDefinition[R]) = { + rule.localUsersSupport match { + case users: LocalUsersSupport.AvailableLocalUsers[R] => users.definedLocalUsers(rule.rule) + case LocalUsersSupport.NotAvailableLocalUsers() => LocalUsers.empty + } + } + + private def coreDecoder(httpClientFactory: HttpClientsFactory, + ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, + globalSettings: GlobalSettings, + mocksProvider: MocksProvider): AsyncDecoder[Core] = { + AsyncDecoderCreator.instance[Core] { c => + val decoder = for { + dynamicVariableTransformationAliases <- + AsyncDecoderCreator.from(VariableTransformationAliasesDefinitionsDecoder.create(systemContext.variablesFunctions)) + variableCreator = new RuntimeResolvableVariableCreator( + TransformationCompiler.withAliases( + systemContext.variablesFunctions, + dynamicVariableTransformationAliases.items.map(_.alias) + ) + ) + auditingTools <- AsyncDecoderCreator.from(AuditingSettingsDecoder.instance(esEnv)) + authProxies <- AsyncDecoderCreator.from(ProxyAuthDefinitionsDecoder.instance) + authenticationServices <- AsyncDecoderCreator.from(ExternalAuthenticationServicesDecoder.instance(httpClientFactory)) + authorizationServices <- AsyncDecoderCreator.from(ExternalAuthorizationServicesDecoder.instance(httpClientFactory)) + jwtDefs <- AsyncDecoderCreator.from(JwtDefinitionsDecoder.instance(httpClientFactory, variableCreator)) + ldapServices <- LdapServicesDecoder.ldapServicesDefinitionsDecoder(using ldapConnectionPoolProvider, systemContext.clock) + rorKbnDefs <- AsyncDecoderCreator.from(RorKbnDefinitionsDecoder.instance(variableCreator)) + impersonationDefinitionsDecoderCreator = new ImpersonationDefinitionsDecoderCreator( + globalSettings, authenticationServices, authProxies, ldapServices, mocksProvider + ) + impersonationDefs <- AsyncDecoderCreator.from(impersonationDefinitionsDecoderCreator.create) + userDefs <- AsyncDecoderCreator.from(UsersDefinitionsDecoder.instance( + authenticationServices, + authorizationServices, + authProxies, + jwtDefs, + rorKbnDefs, + ldapServices, + Some(impersonationDefs), + mocksProvider, + globalSettings + )) + obfuscatedHeaders <- AsyncDecoderCreator.from(obfuscatedHeadersAsyncDecoder) + blocksNel <- { + implicit val loggingContext: LoggingContext = LoggingContext(obfuscatedHeaders) + implicit val blockAsyncDecoder: AsyncDecoder[BlockDecodingResult] = AsyncDecoderCreator.from { + blockDecoder( + DefinitionsPack( + proxies = authProxies, + users = userDefs, + authenticationServices = authenticationServices, + authorizationServices = authorizationServices, + jwts = jwtDefs, + rorKbns = rorKbnDefs, + ldaps = ldapServices, + impersonators = impersonationDefs, + variableTransformationAliases = dynamicVariableTransformationAliases, + ), + globalSettings, + mocksProvider, + ) + } + DecoderHelpers + .decodeFieldList[BlockDecodingResult, Task](Attributes.acl, RulesLevelCreationError.apply) + .emapE { + case NoField => Left(BlocksLevelCreationError(Message(s"No ${Attributes.acl.show} section found"))) + case FieldListValue(blocks) => + NonEmptyList.fromList(blocks) match { + case None => + Left(BlocksLevelCreationError(Message(s"${Attributes.acl.show} defined, but no block found"))) + case Some(neBlocks) => + neBlocks.map(_.block.name).toList.findDuplicates match { + case Nil => Right(neBlocks) + case duplicates => Left(BlocksLevelCreationError(Message(s"Blocks must have unique names. Duplicates: ${duplicates.show}"))) + } + } + } + } + } yield { + val blocks = blocksNel.map(_.block) + blocks.toList.foreach { block => logger.info(s"ADDING BLOCK:\t ${block.show}") } + val localUsers: LocalUsers = { + val fromUserDefs = localUsersFromUserDefs(userDefs) + val fromImpersonatorDefs = localUsersFromImpersonatorDefs(impersonationDefs) + val fromBlocks = blocksNel.map(_.localUsers).toList + (fromBlocks :+ fromUserDefs :+ fromImpersonatorDefs).combineAll + } + + val rorDependencies = RorDependencies( + services = RorDependencies.Services( + authenticationServices = authenticationServices.items.map(_.id), + authorizationServices = authorizationServices.items.map(_.id), + ldaps = ldapServices.items.map(_.id) + ), + localUsers = localUsers, + impersonationWarningsReader = new ImpersonationWarningsCombinedReader(blocksNel.map(_.impersonationWarnings).toList: _*), + ) + val accessControl = new EnabledAccessControlList( + blocks, + new AccessControlListStaticContext( + blocks, + globalSettings, + obfuscatedHeaders + ) + ): AccessControlList + Core(accessControl, rorDependencies, auditingTools) + } + decoder.apply(c) + } + } + + private def localUsersFromUserDefs(definitions: Definitions[UserDef]) = { + definitions.items + .flatMap { definition => + List( + localUsersFromUsernamePatterns(definition.usernames, unknownUsersForWildcardPattern = true), + localUsersFromMode(definition.mode) + ) + } + .combineAll + } + + private def localUsersFromImpersonatorDefs(definitions: Definitions[ImpersonatorDef]) = { + definitions.items + .map(_.impersonatedUsers.usernames) + .map(localUsersFromUsernamePatterns(_, unknownUsersForWildcardPattern = false)) + .combineAll + } + + private def localUsersFromUsernamePatterns(userIdPatterns: UserIdPatterns, + unknownUsersForWildcardPattern: Boolean): LocalUsers = { + userIdPatterns + .patterns + .map { userIdPattern => + if (userIdPattern.containsWildcard) { + LocalUsers(users = Set.empty, unknownUsers = unknownUsersForWildcardPattern) + } else { + LocalUsers(users = Set(userIdPattern.value), unknownUsers = false) + } + } + .toList + .combineAll + } + + private def localUsersFromMode(mode: UserDef.Mode): LocalUsers = { + def localUsersFor(support: EligibleUsersSupport) = support match { + case EligibleUsersSupport.Available(users) => LocalUsers(users, unknownUsers = false) + case EligibleUsersSupport.NotAvailable => LocalUsers.empty + } + + mode match { + case Mode.WithoutGroupsMapping(rule, _) => localUsersFor(rule.eligibleUsers) + case Mode.WithGroupsMapping(Auth.SeparateRules(rule, _), _) => localUsersFor(rule.eligibleUsers) + case Mode.WithGroupsMapping(Auth.SingleRule(rule), _) => localUsersFor(rule.eligibleUsers) + } + } +} + +object RawRorSettingsBasedCoreFactory { + + sealed trait CoreCreationError { + def reason: Reason + } + + object CoreCreationError { + + final case class GeneralReadonlyrestSettingsError(reason: Reason) extends CoreCreationError + final case class DefinitionsLevelCreationError(reason: Reason) extends CoreCreationError + final case class BlocksLevelCreationError(reason: Reason) extends CoreCreationError + final case class RulesLevelCreationError(reason: Reason) extends CoreCreationError + final case class ValueLevelCreationError(reason: Reason) extends CoreCreationError + final case class AuditingSettingsCreationError(reason: Reason) extends CoreCreationError + + sealed trait Reason + object Reason { + + final case class Message(value: String) extends Reason + final case class MalformedValue private(value: String) extends Reason + object MalformedValue { + def fromString(raw: String): MalformedValue = { + val normalized = raw.replaceAll("\r\n?", "\n") + MalformedValue(normalized) + } + + def apply(json: Json): MalformedValue = from(json) + + def from(json: Json): MalformedValue = MalformedValue { + YamlOps.jsonToYamlString(json) + } + } + + } + + } + + private class ImpersonationWarningsCombinedReader(readers: ImpersonationWarningsReader*) + extends ImpersonationWarningsReader { + + override def read() + (implicit requestId: RequestId): List[ImpersonationWarning] = readers.flatMap(_.read()).toList + } + + private class BlockImpersonationWarningsReader[R <: Rule](blockName: Block.Name, + blockRules: NonEmptyList[RuleDefinition[R]]) + extends ImpersonationWarningsReader { + + override def read() + (implicit request: RequestId): List[ImpersonationWarning] = { + blockRules + .toList + .flatMap(impersonationWarningForRule(_)) + } + + private def impersonationWarningForRule(rule: RuleDefinition[R]) + (implicit requestId: RequestId): List[ImpersonationWarning] = { + rule.impersonationWarnings match { + case extractor: ImpersonationWarningSupport.ImpersonationWarningExtractor[_] => + extractor.warningFor(rule.rule, blockName).toList + case ImpersonationWarningSupport.NotSupported() => + List.empty + } + } + } + + private case class BlockDecodingResult(block: Block, + localUsers: LocalUsers, + impersonationWarnings: ImpersonationWarningsReader) + + private sealed trait RuleDecodingResult + private object RuleDecodingResult { + final case class Result(value: Decoder.Result[RuleDefinition[Rule]]) extends RuleDecodingResult + case object UnknownRule extends RuleDecodingResult + case object Skipped extends RuleDecodingResult + } + + private object Attributes { + val rorSectionName = "readonlyrest" + val acl = "access_control_rules" + + object Block { + val name = "name" + val policy = "type" + val verbosity = "verbosity" + val audit = "audit" + } + + } + +} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/GlobalSettings.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/GlobalSettings.scala index 1ee9da08ff..493e6d801d 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/GlobalSettings.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/GlobalSettings.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.accesscontrol.factory -import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, RorConfigurationIndex} +import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, RorSettingsIndex} final case class GlobalSettings(showBasicAuthPrompt: Boolean, forbiddenRequestMessage: String, flsEngine: GlobalSettings.FlsEngine, - configurationIndex: RorConfigurationIndex, + settingsIndex: RorSettingsIndex, userIdCaseSensitivity: CaseSensitivity, usersDefinitionDuplicateUsernamesValidationEnabled: Boolean) diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/HttpClientsFactory.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/HttpClientsFactory.scala index 1ac8e2218d..40c7f8104f 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/HttpClientsFactory.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/HttpClientsFactory.scala @@ -81,7 +81,7 @@ object HttpClientsFactory { } // todo: remove synchronized, use more sophisticated lock mechanism -class AsyncHttpClientsFactory extends HttpClientsFactory { +class AsyncHttpClientsFactory extends HttpClientsFactory with Logging { private val existingClients = new CopyOnWriteArrayList[AsyncHttpClient]() private val isWorking = AtomicBoolean(true) @@ -102,17 +102,23 @@ class AsyncHttpClientsFactory extends HttpClientsFactory { } private def newAsyncHttpClient(config: Config) = { - val timer = new HashedWheelTimer - val maxIdleTimeout = 60.seconds - val connectionTtl = -1.milliseconds - val cleanerPeriod = -1.milliseconds - val pool = new DefaultChannelPool(maxIdleTimeout.toJava, connectionTtl.toJava, DefaultChannelPool.PoolLeaseStrategy.FIFO, timer, cleanerPeriod.toJava) - asyncHttpClient { - new DefaultAsyncHttpClientConfig.Builder() - .setNettyTimer(timer) - .setChannelPool(pool) - .setUseInsecureTrustManager(!config.validate) - .build() + try { + val timer = new HashedWheelTimer + val maxIdleTimeout = 60.seconds + val connectionTtl = -1.milliseconds + val cleanerPeriod = -1.milliseconds + val pool = new DefaultChannelPool(maxIdleTimeout.toJava, connectionTtl.toJava, DefaultChannelPool.PoolLeaseStrategy.FIFO, timer, cleanerPeriod.toJava) + asyncHttpClient { + new DefaultAsyncHttpClientConfig.Builder() + .setNettyTimer(timer) + .setChannelPool(pool) + .setUseInsecureTrustManager(!config.validate) + .build() + } + } catch { + case ex: Throwable => + logger.error("ERR: ", ex) + throw ex } } } diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/JsonConfigStaticVariableResolver.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/JsonConfigStaticVariableResolver.scala deleted file mode 100644 index 0d31655771..0000000000 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/JsonConfigStaticVariableResolver.scala +++ /dev/null @@ -1,119 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.accesscontrol.factory - -import cats.data.NonEmptyList -import eu.timepit.refined.types.string.NonEmptyString -import io.circe.Json -import tech.beshu.ror.accesscontrol.blocks.variables.startup.StartupResolvableVariableCreator -import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler -import tech.beshu.ror.accesscontrol.factory.JsonConfigStaticVariableResolver.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.providers.EnvVarsProvider - -class JsonConfigStaticVariableResolver(envProvider: EnvVarsProvider, - transformationCompiler: TransformationCompiler) { - - private val variableCreator = new StartupResolvableVariableCreator(transformationCompiler) - - def resolve(json: Json): Either[NonEmptyList[ResolvingError], Json] = { - val errors = ResolvingErrors(Vector.empty) - val jsonWithResolvedVars = mapJson(json, errors) - errors.values.toList match { - case Nil => Right(jsonWithResolvedVars) - case resolvingErrors => Left(NonEmptyList.fromListUnsafe(resolvingErrors)) - } - } - - private def mapJson(json: Json, errors: ResolvingErrors): Json = { - json - .mapArray(_.flatMap { json => - json.asString.flatMap(NonEmptyString.unapply) match { - case Some(str) => - tryToResolveAllStaticMultipleVars(str, errors) - .map(s => resolvedStringToJson(s, json)) - .toList - case None => - mapJson(json, errors) :: Nil - } - }) - .mapBoolean(identity) - .mapNumber(identity) - .mapObject(_.mapValues(mapJson(_, errors))) - .withString { str => - val resolved = NonEmptyString.unapply(str) match { - case Some(nes) => - tryToResolveAllStaticSingleVars(nes, errors) - case None => str - } - if (resolved =!= str) - resolvedStringToJson(resolved, json) - else json - } - } - - - private def resolvedStringToJson(resolvedStr: String, original: Json) = { - def isJsonPrimitive(json: Json) = !(json.isObject || json.isArray) - - def preserveNumericStringsAsStrings(newValue: Json) = - if (newValue.asNumber.map(_.toString()) == original.asString) original else newValue - - io.circe.parser.parse(resolvedStr) match { - case Right(newJsonValue) if isJsonPrimitive(newJsonValue) => - preserveNumericStringsAsStrings(newJsonValue) - case Right(_) => Json.fromString(resolvedStr) - case Left(_) => Json.fromString(resolvedStr) - } - } - - - private def tryToResolveAllStaticSingleVars(str: NonEmptyString, errors: ResolvingErrors): String = { - variableCreator.createSingleVariableFrom(str) match { - case Right(variable) => - variable.resolve(envProvider) match { - case Right(extracted) => extracted - case Left(error) => - errors.values = errors.values :+ ResolvingError(error.msg) - str.value - } - case Left(error) => - errors.values = errors.values :+ ResolvingError(error.show) - str.value - } - } - - private def tryToResolveAllStaticMultipleVars(str: NonEmptyString, errors: ResolvingErrors): NonEmptyList[String] = { - variableCreator.createMultiVariableFrom(str) match { - case Right(variable) => - variable.resolve(envProvider) match { - case Right(extracted) => extracted - case Left(error) => - errors.values = errors.values :+ ResolvingError(error.msg) - NonEmptyList.one(str.value) - } - case Left(error) => - errors.values = errors.values :+ ResolvingError(error.show) - NonEmptyList.one(str.value) - } - } -} - -object JsonConfigStaticVariableResolver { - final case class ResolvingError(msg: String) extends AnyVal - private final case class ResolvingErrors(var values: Vector[ResolvingError]) -} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/JsonStaticVariablesResolver.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/JsonStaticVariablesResolver.scala new file mode 100644 index 0000000000..18bc99a71b --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/JsonStaticVariablesResolver.scala @@ -0,0 +1,117 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.accesscontrol.factory + +import cats.data.NonEmptyList +import eu.timepit.refined.types.string.NonEmptyString +import io.circe.Json +import tech.beshu.ror.accesscontrol.blocks.variables.startup.StartupResolvableVariableCreator +import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler +import tech.beshu.ror.accesscontrol.factory.JsonStaticVariablesResolver.* +import tech.beshu.ror.implicits.* +import tech.beshu.ror.providers.EnvVarsProvider + +class JsonStaticVariablesResolver(envProvider: EnvVarsProvider, + transformationCompiler: TransformationCompiler) { + + private val variableCreator = new StartupResolvableVariableCreator(transformationCompiler) + + def resolve(json: Json): Either[NonEmptyList[ResolvingError], Json] = { + val errors = ResolvingErrors(Vector.empty) + val jsonWithResolvedVars = mapJson(json, errors) + errors.values.toList match { + case Nil => Right(jsonWithResolvedVars) + case resolvingErrors => Left(NonEmptyList.fromListUnsafe(resolvingErrors)) + } + } + + private def mapJson(json: Json, errors: ResolvingErrors): Json = { + json + .mapArray(_.flatMap { json => + json.asString.flatMap(NonEmptyString.unapply) match { + case Some(str) => + tryToResolveAllStaticMultipleVars(str, errors) + .map(s => resolvedStringToJson(s, json)) + .toList + case None => + mapJson(json, errors) :: Nil + } + }) + .mapBoolean(identity) + .mapNumber(identity) + .mapObject(_.mapValues(mapJson(_, errors))) + .withString { str => + val resolved = NonEmptyString.unapply(str) match { + case Some(nes) => + tryToResolveAllStaticSingleVars(nes, errors) + case None => str + } + if (resolved =!= str) + resolvedStringToJson(resolved, json) + else json + } + } + + private def resolvedStringToJson(resolvedStr: String, original: Json) = { + def isJsonPrimitive(json: Json) = !(json.isObject || json.isArray) + + def preserveNumericStringsAsStrings(newValue: Json) = + if (newValue.asNumber.map(_.toString()) == original.asString) original else newValue + + io.circe.parser.parse(resolvedStr) match { + case Right(newJsonValue) if isJsonPrimitive(newJsonValue) => + preserveNumericStringsAsStrings(newJsonValue) + case Right(_) => Json.fromString(resolvedStr) + case Left(_) => Json.fromString(resolvedStr) + } + } + + private def tryToResolveAllStaticSingleVars(str: NonEmptyString, errors: ResolvingErrors): String = { + variableCreator.createSingleVariableFrom(str) match { + case Right(variable) => + variable.resolve(envProvider) match { + case Right(extracted) => extracted + case Left(error) => + errors.values = errors.values :+ ResolvingError(error.msg) + str.value + } + case Left(error) => + errors.values = errors.values :+ ResolvingError(error.show) + str.value + } + } + + private def tryToResolveAllStaticMultipleVars(str: NonEmptyString, errors: ResolvingErrors): NonEmptyList[String] = { + variableCreator.createMultiVariableFrom(str) match { + case Right(variable) => + variable.resolve(envProvider) match { + case Right(extracted) => extracted + case Left(error) => + errors.values = errors.values :+ ResolvingError(error.msg) + NonEmptyList.one(str.value) + } + case Left(error) => + errors.values = errors.values :+ ResolvingError(error.show) + NonEmptyList.one(str.value) + } + } +} + +object JsonStaticVariablesResolver { + final case class ResolvingError(msg: String) extends AnyVal + private final case class ResolvingErrors(var values: Vector[ResolvingError]) +} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/RawRorConfigBasedCoreFactory.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/RawRorConfigBasedCoreFactory.scala deleted file mode 100644 index f16dc60238..0000000000 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/RawRorConfigBasedCoreFactory.scala +++ /dev/null @@ -1,559 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.accesscontrol.factory - -import cats.data.{NonEmptyList, State, Validated} -import cats.kernel.Monoid -import io.circe.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.* -import tech.beshu.ror.accesscontrol.EnabledAccessControlList.AccessControlListStaticContext -import tech.beshu.ror.accesscontrol.audit.LoggingContext -import tech.beshu.ror.accesscontrol.blocks.Block.{RuleDefinition, Verbosity} -import tech.beshu.ror.accesscontrol.blocks.ImpersonationWarning.ImpersonationWarningSupport -import tech.beshu.ror.accesscontrol.blocks.definitions.UserDef.Mode -import tech.beshu.ror.accesscontrol.blocks.definitions.UserDef.Mode.WithGroupsMapping.Auth -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider -import tech.beshu.ror.accesscontrol.blocks.definitions.{ImpersonatorDef, UserDef} -import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider -import tech.beshu.ror.accesscontrol.blocks.rules.Rule -import tech.beshu.ror.accesscontrol.blocks.rules.Rule.AuthenticationRule.EligibleUsersSupport -import tech.beshu.ror.accesscontrol.blocks.users.LocalUsersContext.{LocalUsersSupport, localUsersMonoid} -import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariableCreator -import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler -import tech.beshu.ror.accesscontrol.blocks.{Block, ImpersonationWarning} -import tech.beshu.ror.accesscontrol.domain.* -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.* -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.* -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.decoders.definitions.* -import tech.beshu.ror.accesscontrol.factory.decoders.ruleDecoders.ruleDecoderBy -import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleDecoder -import tech.beshu.ror.accesscontrol.factory.decoders.{AuditingSettingsDecoder, GlobalStaticSettingsDecoder} -import tech.beshu.ror.accesscontrol.utils.* -import tech.beshu.ror.accesscontrol.utils.CirceOps.* -import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers.FieldListResult.{FieldListValue, NoField} -import tech.beshu.ror.configuration.RorConfig.ImpersonationWarningsReader -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig, RorConfig} -import tech.beshu.ror.es.EsVersion -import tech.beshu.ror.implicits.* -import tech.beshu.ror.syntax.* -import tech.beshu.ror.utils.ScalaOps.* -import tech.beshu.ror.utils.yaml.YamlOps - -final case class Core(accessControl: AccessControlList, - rorConfig: RorConfig) - -trait CoreFactory { - def createCoreFrom(config: RawRorConfig, - rorIndexNameConfiguration: RorConfigurationIndex, - httpClientFactory: HttpClientsFactory, - ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, - mocksProvider: MocksProvider): Task[Either[NonEmptyList[CoreCreationError], Core]] -} - -class RawRorConfigBasedCoreFactory(esVersion: EsVersion) - (implicit environmentConfig: EnvironmentConfig) - extends CoreFactory with Logging { - - override def createCoreFrom(config: RawRorConfig, - rorIndexNameConfiguration: RorConfigurationIndex, - httpClientFactory: HttpClientsFactory, - ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, - mocksProvider: MocksProvider): Task[Either[NonEmptyList[CoreCreationError], Core]] = { - config.configJson \\ Attributes.rorSectionName match { - case Nil => createCoreFromRorSection( - config.configJson, - rorIndexNameConfiguration, - httpClientFactory, - ldapConnectionPoolProvider, - mocksProvider - ) - case rorSection :: Nil => createCoreFromRorSection( - rorSection, - rorIndexNameConfiguration, - httpClientFactory, - ldapConnectionPoolProvider, - mocksProvider - ) - case _ => Task.now(Left(NonEmptyList.one(GeneralReadonlyrestSettingsError(Message(s"Malformed settings"))))) - } - } - - private def createCoreFromRorSection(rorSection: Json, - rorIndexNameConfiguration: RorConfigurationIndex, - httpClientFactory: HttpClientsFactory, - ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, - mocksProvider: MocksProvider) = { - val jsonConfigResolver = new JsonConfigStaticVariableResolver( - environmentConfig.envVarsProvider, - TransformationCompiler.withoutAliases(environmentConfig.variablesFunctions), - ) - jsonConfigResolver.resolve(rorSection) match { - case Right(resolvedRorSection) => - createFrom(resolvedRorSection, rorIndexNameConfiguration, httpClientFactory, ldapConnectionPoolProvider, mocksProvider).map { - case Right(settings) => - Right(settings) - case Left(failure) => - Left(NonEmptyList.one(failure.aclCreationError.getOrElse(GeneralReadonlyrestSettingsError(Message(s"Malformed settings"))))) - } - case Left(errors) => - Task.now(Left(errors.map(e => GeneralReadonlyrestSettingsError(Message(e.msg))))) - } - } - - private def createFrom(settingsJson: Json, - rorConfigurationIndex: RorConfigurationIndex, - httpClientFactory: HttpClientsFactory, - ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, - mocksProvider: MocksProvider) = { - val decoder = for { - enabled <- AsyncDecoderCreator.from(coreEnabilityDecoder) - core <- - if (!enabled) { - AsyncDecoderCreator - .from(Decoder.const(Core(DisabledAccessControlList, RorConfig.disabled))) - } else { - for { - globalSettings <- AsyncDecoderCreator.from(GlobalStaticSettingsDecoder.instance(rorConfigurationIndex)) - core <- coreDecoder(httpClientFactory, ldapConnectionPoolProvider, globalSettings, mocksProvider) - } yield core - } - } yield core - - decoder(HCursor.fromJson(settingsJson)) - } - - private def coreEnabilityDecoder: Decoder[Boolean] = { - Decoder.instance { c => - for { - enabled <- c.downField("enable").as[Option[Boolean]] - } yield enabled.getOrElse(true) - } - } - - import RawRorConfigBasedCoreFactory.* - - private def rulesNelDecoder(definitions: DefinitionsPack, - globalSettings: GlobalSettings, - mocksProvider: MocksProvider): Decoder[NonEmptyList[RuleDefinition[Rule]]] = Decoder.instance { c => - val init = State.pure[ACursor, Validated[List[String], Decoder.Result[List[RuleDefinition[Rule]]]]](Validated.Valid(Right(List.empty))) - - val (_, result) = c.keys.toList.flatten // at the moment kibana_index must be defined before kibana_access - .foldLeft(init) { case (collectedRuleResults, currentRuleName) => - for { - last <- collectedRuleResults - current <- decodeRuleInCursorContext(currentRuleName, definitions, globalSettings, mocksProvider).map { - case RuleDecodingResult.Result(value) => Validated.Valid(value.map(_ :: Nil)) - case RuleDecodingResult.UnknownRule => Validated.Invalid(currentRuleName :: Nil) - case RuleDecodingResult.Skipped => Validated.Valid(Right(List.empty)) - } - } yield Monoid.combine(last, current) - } - .run(c) - .value - - result match { - case Validated.Valid(r) => - r.flatMap { a => - NonEmptyList.fromList(a) match { - case Some(rules) => - Right(rules) - case None => - Left(DecodingFailureOps.fromError(RulesLevelCreationError(Message(s"No rules defined in block")))) - } - } - case Validated.Invalid(unknownRules) => - Left(DecodingFailureOps.fromError(RulesLevelCreationError(Message(s"Unknown rules: ${unknownRules.show}")))) - } - } - - private def decodeRuleInCursorContext(name: String, - definitions: DefinitionsPack, - globalSettings: GlobalSettings, - mocksProvider: MocksProvider): State[ACursor, RuleDecodingResult] = { - State(cursor => { - if (!cursor.keys.toList.flatten.contains(name)) { - (cursor, RuleDecodingResult.Skipped) - } else { - ruleDecoderBy(Rule.Name(name), definitions, globalSettings, mocksProvider) match { - case Some(decoder) => - decoder.tryDecode(cursor) match { - case Right(RuleDecoder.Result(rule, unconsumedCursor)) => - (unconsumedCursor, RuleDecodingResult.Result(Right(rule))) - case Left(failure) => - (cursor, RuleDecodingResult.Result(Left(failure))) - } - case None => - (cursor, RuleDecodingResult.UnknownRule) - } - } - }) - } - - private def blockDecoder(definitions: DefinitionsPack, - globalSettings: GlobalSettings, - mocksProvider: MocksProvider) - (implicit loggingContext: LoggingContext): Decoder[BlockDecodingResult] = { - implicit val nameDecoder: Decoder[Block.Name] = DecoderHelpers.decodeStringLike.map(Block.Name.apply) - implicit val policyDecoder: Decoder[Block.Policy] = this.policyDecoder - implicit val verbosityDecoder: Decoder[Verbosity] = - Decoder - .decodeString - .toSyncDecoder - .emapE[Verbosity] { - case "info" => Right(Verbosity.Info) - case "error" => Right(Verbosity.Error) - case unknown => Left(BlocksLevelCreationError(Message(s"Unknown verbosity value: ${unknown.show}. Supported types: 'info'(default), 'error'."))) - }.decoder - implicit val blockAuditDecoder: Decoder[Block.Audit] = - Decoder.instance { c => - for { - enabled <- c.downField("enabled").as[Boolean] - } yield { - if (enabled) Block.Audit.Enabled else Block.Audit.Disabled - } - } - Decoder - .instance { c => - val result = for { - name <- c.downField(Attributes.Block.name).as[Block.Name] - policy <- c.downField(Attributes.Block.policy).as[Option[Block.Policy]] - verbosity <- c.downField(Attributes.Block.verbosity).as[Option[Block.Verbosity]] - audit <- c.downField(Attributes.Block.audit).as[Option[Block.Audit]] - rules <- rulesNelDecoder(definitions, globalSettings, mocksProvider) - .toSyncDecoder - .decoder - .tryDecode(c.withFocus( - _.mapObject(_ - .remove(Attributes.Block.name) - .remove(Attributes.Block.policy) - .remove(Attributes.Block.verbosity) - .remove(Attributes.Block.audit) - ) - )) - block <- Block.createFrom(name, policy, verbosity, audit, rules).left.map(DecodingFailureOps.fromError(_)) - } yield BlockDecodingResult( - block = block, - localUsers = rules.map(localUsersForRule).combineAll, - impersonationWarnings = new BlockImpersonationWarningsReader(block.name, rules) - ) - result.left.map(_.overrideDefaultErrorWith(BlocksLevelCreationError(MalformedValue(c.value)))) - } - } - - private val obfuscatedHeadersAsyncDecoder: Decoder[Set[Header.Name]] = { - import tech.beshu.ror.accesscontrol.factory.decoders.common.headerName - Decoder.instance(_.downField("obfuscated_headers").as[Option[Set[Header.Name]]]) - .map(_.getOrElse(Set(Header.Name.authorization))) - } - - private val policyDecoder: Decoder[Block.Policy] = { - def unknownTypeError(unknownType: String) = - BlocksLevelCreationError(Message( - s"Unknown block policy type: ${unknownType.show}. Supported types: 'allow'(default), 'forbid'." - )) - - val simplePolicyDecoder = { - Decoder - .decodeString - .toSyncDecoder - .emapE[Block.Policy] { - case "allow" => Right(Block.Policy.Allow) - case "forbid" => Right(Block.Policy.Forbid()) - case unknown => Left(unknownTypeError(unknown)) - } - .decoder - } - - val extendedPolicyDecoder = { - Decoder - .instance { c => - for { - policyType <- c.downFieldAs[String]("policy") - policy <- policyType match { - case "allow" => Right(Block.Policy.Allow) - case "forbid" => c.downFieldAs[Option[String]]("response_message").map(Block.Policy.Forbid.apply) - case unknown => Left(DecodingFailureOps.fromError(unknownTypeError(unknown))) - } - } yield policy - } - .toSyncDecoder - .decoder - } - - Decoder.instance { c => - c.focus match { - case Some(f) if f.isString => simplePolicyDecoder(c) - case Some(f) if f.isObject => extendedPolicyDecoder(c) - case Some(_) | None => Left(DecodingFailure("Malformed block policy type", c.history)) - } - } - } - - private def localUsersForRule[R <: Rule](rule: RuleDefinition[R]) = { - rule.localUsersSupport match { - case users: LocalUsersSupport.AvailableLocalUsers[R] => users.definedLocalUsers(rule.rule) - case LocalUsersSupport.NotAvailableLocalUsers() => LocalUsers.empty - } - } - - private def coreDecoder(httpClientFactory: HttpClientsFactory, - ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, - globalSettings: GlobalSettings, - mocksProvider: MocksProvider): AsyncDecoder[Core] = { - AsyncDecoderCreator.instance[Core] { c => - val decoder = for { - dynamicVariableTransformationAliases <- - AsyncDecoderCreator.from(VariableTransformationAliasesDefinitionsDecoder.create(environmentConfig.variablesFunctions)) - variableCreator = new RuntimeResolvableVariableCreator( - TransformationCompiler.withAliases( - environmentConfig.variablesFunctions, - dynamicVariableTransformationAliases.items.map(_.alias) - ) - ) - auditingTools <- AsyncDecoderCreator.from(AuditingSettingsDecoder.instance(esVersion)) - authProxies <- AsyncDecoderCreator.from(ProxyAuthDefinitionsDecoder.instance) - authenticationServices <- AsyncDecoderCreator.from(ExternalAuthenticationServicesDecoder.instance(httpClientFactory)) - authorizationServices <- AsyncDecoderCreator.from(ExternalAuthorizationServicesDecoder.instance(httpClientFactory)) - jwtDefs <- AsyncDecoderCreator.from(JwtDefinitionsDecoder.instance(httpClientFactory, variableCreator)) - ldapServices <- LdapServicesDecoder.ldapServicesDefinitionsDecoder(using ldapConnectionPoolProvider, environmentConfig.clock) - rorKbnDefs <- AsyncDecoderCreator.from(RorKbnDefinitionsDecoder.instance(variableCreator)) - impersonationDefinitionsDecoderCreator = new ImpersonationDefinitionsDecoderCreator( - globalSettings, authenticationServices, authProxies, ldapServices, mocksProvider - ) - impersonationDefs <- AsyncDecoderCreator.from(impersonationDefinitionsDecoderCreator.create) - userDefs <- AsyncDecoderCreator.from(UsersDefinitionsDecoder.instance( - authenticationServices, - authorizationServices, - authProxies, - jwtDefs, - rorKbnDefs, - ldapServices, - Some(impersonationDefs), - mocksProvider, - globalSettings - )) - obfuscatedHeaders <- AsyncDecoderCreator.from(obfuscatedHeadersAsyncDecoder) - blocksNel <- { - implicit val loggingContext: LoggingContext = LoggingContext(obfuscatedHeaders) - implicit val blockAsyncDecoder: AsyncDecoder[BlockDecodingResult] = AsyncDecoderCreator.from { - blockDecoder( - DefinitionsPack( - proxies = authProxies, - users = userDefs, - authenticationServices = authenticationServices, - authorizationServices = authorizationServices, - jwts = jwtDefs, - rorKbns = rorKbnDefs, - ldaps = ldapServices, - impersonators = impersonationDefs, - variableTransformationAliases = dynamicVariableTransformationAliases, - ), - globalSettings, - mocksProvider, - ) - } - DecoderHelpers - .decodeFieldList[BlockDecodingResult, Task](Attributes.acl, RulesLevelCreationError.apply) - .emapE { - case NoField => Left(BlocksLevelCreationError(Message(s"No ${Attributes.acl.show} section found"))) - case FieldListValue(blocks) => - NonEmptyList.fromList(blocks) match { - case None => - Left(BlocksLevelCreationError(Message(s"${Attributes.acl.show} defined, but no block found"))) - case Some(neBlocks) => - neBlocks.map(_.block.name).toList.findDuplicates match { - case Nil => Right(neBlocks) - case duplicates => Left(BlocksLevelCreationError(Message(s"Blocks must have unique names. Duplicates: ${duplicates.show}"))) - } - } - } - } - } yield { - val blocks = blocksNel.map(_.block) - blocks.toList.foreach { block => logger.info(s"ADDING BLOCK:\t ${block.show}") } - val localUsers: LocalUsers = { - val fromUserDefs = localUsersFromUserDefs(userDefs) - val fromImpersonatorDefs = localUsersFromImpersonatorDefs(impersonationDefs) - val fromBlocks = blocksNel.map(_.localUsers).toList - (fromBlocks :+ fromUserDefs :+ fromImpersonatorDefs).combineAll - } - - val rorConfig = RorConfig( - services = RorConfig.Services( - authenticationServices = authenticationServices.items.map(_.id), - authorizationServices = authorizationServices.items.map(_.id), - ldaps = ldapServices.items.map(_.id) - ), - localUsers = localUsers, - impersonationWarningsReader = new ImpersonationWarningsCombinedReader(blocksNel.map(_.impersonationWarnings).toList: _*), - auditingSettings = auditingTools, - ) - val accessControl = new EnabledAccessControlList( - blocks, - new AccessControlListStaticContext( - blocks, - globalSettings, - obfuscatedHeaders - ) - ): AccessControlList - Core(accessControl, rorConfig) - } - decoder.apply(c) - } - } - - private def localUsersFromUserDefs(definitions: Definitions[UserDef]) = { - definitions.items - .flatMap { definition => - List( - localUsersFromUsernamePatterns(definition.usernames, unknownUsersForWildcardPattern = true), - localUsersFromMode(definition.mode) - ) - } - .combineAll - } - - private def localUsersFromImpersonatorDefs(definitions: Definitions[ImpersonatorDef]) = { - definitions.items - .map(_.impersonatedUsers.usernames) - .map(localUsersFromUsernamePatterns(_, unknownUsersForWildcardPattern = false)) - .combineAll - } - - private def localUsersFromUsernamePatterns(userIdPatterns: UserIdPatterns, - unknownUsersForWildcardPattern: Boolean): LocalUsers = { - userIdPatterns - .patterns - .map { userIdPattern => - if (userIdPattern.containsWildcard) { - LocalUsers(users = Set.empty, unknownUsers = unknownUsersForWildcardPattern) - } else { - LocalUsers(users = Set(userIdPattern.value), unknownUsers = false) - } - } - .toList - .combineAll - } - - private def localUsersFromMode(mode: UserDef.Mode): LocalUsers = { - def localUsersFor(support: EligibleUsersSupport) = support match { - case EligibleUsersSupport.Available(users) => LocalUsers(users, unknownUsers = false) - case EligibleUsersSupport.NotAvailable => LocalUsers.empty - } - - mode match { - case Mode.WithoutGroupsMapping(rule, _) => localUsersFor(rule.eligibleUsers) - case Mode.WithGroupsMapping(Auth.SeparateRules(rule, _), _) => localUsersFor(rule.eligibleUsers) - case Mode.WithGroupsMapping(Auth.SingleRule(rule), _) => localUsersFor(rule.eligibleUsers) - } - } -} - -object RawRorConfigBasedCoreFactory { - - sealed trait CoreCreationError { - def reason: Reason - } - - object CoreCreationError { - - final case class GeneralReadonlyrestSettingsError(reason: Reason) extends CoreCreationError - final case class DefinitionsLevelCreationError(reason: Reason) extends CoreCreationError - final case class BlocksLevelCreationError(reason: Reason) extends CoreCreationError - final case class RulesLevelCreationError(reason: Reason) extends CoreCreationError - final case class ValueLevelCreationError(reason: Reason) extends CoreCreationError - final case class AuditingSettingsCreationError(reason: Reason) extends CoreCreationError - - sealed trait Reason - object Reason { - - final case class Message(value: String) extends Reason - final case class MalformedValue private(value: String) extends Reason - object MalformedValue { - def fromString(raw: String): MalformedValue = { - val normalized = raw.replaceAll("\r\n?", "\n") - MalformedValue(normalized) - } - - def apply(json: Json): MalformedValue = from(json) - - def from(json: Json): MalformedValue = MalformedValue { - YamlOps.jsonToYamlString(json) - } - } - - } - - } - - private class ImpersonationWarningsCombinedReader(readers: ImpersonationWarningsReader*) - extends ImpersonationWarningsReader { - - override def read() - (implicit requestId: RequestId): List[ImpersonationWarning] = readers.flatMap(_.read()).toList - } - - private class BlockImpersonationWarningsReader[R <: Rule](blockName: Block.Name, - blockRules: NonEmptyList[RuleDefinition[R]]) - extends ImpersonationWarningsReader { - - override def read() - (implicit request: RequestId): List[ImpersonationWarning] = { - blockRules - .toList - .flatMap(impersonationWarningForRule(_)) - } - - private def impersonationWarningForRule(rule: RuleDefinition[R]) - (implicit requestId: RequestId): List[ImpersonationWarning] = { - rule.impersonationWarnings match { - case extractor: ImpersonationWarningSupport.ImpersonationWarningExtractor[_] => - extractor.warningFor(rule.rule, blockName).toList - case ImpersonationWarningSupport.NotSupported() => - List.empty - } - } - } - - private case class BlockDecodingResult(block: Block, - localUsers: LocalUsers, - impersonationWarnings: ImpersonationWarningsReader) - - private sealed trait RuleDecodingResult - private object RuleDecodingResult { - final case class Result(value: Decoder.Result[RuleDefinition[Rule]]) extends RuleDecodingResult - case object UnknownRule extends RuleDecodingResult - case object Skipped extends RuleDecodingResult - } - - private object Attributes { - val rorSectionName = "readonlyrest" - val acl = "access_control_rules" - - object Block { - val name = "name" - val policy = "type" - val verbosity = "verbosity" - val audit = "audit" - } - - } - -} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/RorDependencies.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/RorDependencies.scala new file mode 100644 index 0000000000..1abc139281 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/RorDependencies.scala @@ -0,0 +1,51 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.accesscontrol.factory + +import tech.beshu.ror.accesscontrol.blocks.ImpersonationWarning +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.LdapService +import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationService, ExternalAuthorizationService} +import tech.beshu.ror.accesscontrol.domain.{LocalUsers, RequestId} +import tech.beshu.ror.accesscontrol.factory.RorDependencies.ImpersonationWarningsReader + +import scala.annotation.unused + +final case class RorDependencies(services: RorDependencies.Services, + localUsers: LocalUsers, + impersonationWarningsReader: ImpersonationWarningsReader) + +object RorDependencies { + def noOp: RorDependencies = RorDependencies(RorDependencies.Services.empty, LocalUsers.empty, NoOpImpersonationWarningsReader) + + final case class Services(authenticationServices: Seq[ExternalAuthenticationService#Id], + authorizationServices: Seq[ExternalAuthorizationService#Id], + ldaps: Seq[LdapService#Id]) + object Services { + def empty: Services = Services(Seq.empty, Seq.empty, Seq.empty) + } + + trait ImpersonationWarningsReader { + def read() + (implicit requestId: RequestId): List[ImpersonationWarning] + } + + object NoOpImpersonationWarningsReader extends ImpersonationWarningsReader { + override def read() + (implicit @unused requestId: RequestId): List[ImpersonationWarning] = List.empty + } + +} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala index 5e33651e77..ff0070c7a2 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/AuditingSettingsDecoder.scala @@ -31,8 +31,8 @@ import tech.beshu.ror.accesscontrol.audit.ecs.EcsV1AuditLogSerializer import tech.beshu.ror.accesscontrol.domain.AuditCluster.{AuditClusterNode, ClusterMode, NodeCredentials, RemoteAuditCluster} import tech.beshu.ror.accesscontrol.domain.RorAuditIndexTemplate.CreationError import tech.beshu.ror.accesscontrol.domain.{AuditCluster, RorAuditDataStream, RorAuditIndexTemplate, RorAuditLoggerName} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{AuditingSettingsCreationError, Reason} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{AuditingSettingsCreationError, Reason} import tech.beshu.ror.accesscontrol.factory.decoders.common.{lemonLabsUriDecoder, nonEmptyStringDecoder} import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.accesscontrol.utils.SyncDecoderCreator @@ -40,7 +40,7 @@ import tech.beshu.ror.audit.AuditLogSerializer import tech.beshu.ror.audit.AuditResponseContext.Verbosity import tech.beshu.ror.audit.adapters.* import tech.beshu.ror.audit.utils.AuditSerializationHelper.{AllowedEventMode, AuditFieldPath, AuditFieldValueDescriptor} -import tech.beshu.ror.es.EsVersion +import tech.beshu.ror.es.{EsEnv, EsNodeSettings, EsVersion} import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList import tech.beshu.ror.utils.yaml.YamlKeyDecoder @@ -50,40 +50,41 @@ import scala.util.{Failure, Success, Try} object AuditingSettingsDecoder extends Logging { - def instance(esVersion: EsVersion): Decoder[Option[AuditingTool.AuditSettings]] = { + def instance(esEnv: EsEnv): Decoder[Option[AuditingTool.AuditSettings]] = { for { - auditSettings <- auditSettingsDecoder(esVersion) - deprecatedAuditSettings <- DeprecatedAuditSettingsDecoder.instance + auditSettings <- auditSettingsDecoder(esEnv) + deprecatedAuditSettings <- DeprecatedAuditSettingsDecoder.instance(esEnv) } yield auditSettings.orElse(deprecatedAuditSettings) } - private def auditSettingsDecoder(esVersion: EsVersion): Decoder[Option[AuditingTool.AuditSettings]] = Decoder.instance { c => + private def auditSettingsDecoder(esEnv: EsEnv): Decoder[Option[AuditingTool.AuditSettings]] = Decoder.instance { c => for { isAuditEnabled <- YamlKeyDecoder[Boolean]( - segments = NonEmptyList.of("audit", "enabled"), + path = NonEmptyList.of("audit", "enabled"), default = false ).apply(c) result <- if (isAuditEnabled) { - decodeAuditSettings(using esVersion)(c).map(Some.apply) + decodeAuditSettings(esEnv)(c).map(Some.apply) } else { Right(None) } } yield result } - private def decodeAuditSettings(using EsVersion) = { - decodeAuditSettingsWith(using auditSinkConfigSimpleDecoder) + private def decodeAuditSettings(esEnv: EsEnv) = { + decodeAuditSettingsWith(esEnv.esNodeSettings)(using auditSinkConfigSimpleDecoder(using esEnv.esVersion)) .handleErrorWith { error => if (error.aclCreationError.isDefined) { // the schema was valid, but the config not Decoder.failed(error) } else { - decodeAuditSettingsWith(using auditSinkConfigExtendedDecoder) + decodeAuditSettingsWith(esEnv.esNodeSettings)(using auditSinkConfigExtendedDecoder(using esEnv.esVersion)) } } } - private def decodeAuditSettingsWith(using Decoder[AuditSink]) = { + private def decodeAuditSettingsWith(esNodeSettings: EsNodeSettings) + (using Decoder[AuditSink]) = { SyncDecoderCreator .instance { _.downField("audit").downField("outputs").as[Option[List[AuditSink]]] @@ -92,11 +93,12 @@ object AuditingSettingsDecoder extends Logging { case Some(outputs) => NonEmptyList .fromList(outputs.distinct) - .map(AuditingTool.AuditSettings.apply) + .map(AuditingTool.AuditSettings(_, esNodeSettings)) .toRight(auditSettingsError(s"The audit 'outputs' array cannot be empty")) case None => AuditingTool.AuditSettings( - NonEmptyList.of(AuditSink.Enabled(EsIndexBasedSink.default)) + NonEmptyList.of(AuditSink.Enabled(EsIndexBasedSink.default)), + esNodeSettings ).asRight } .decoder @@ -558,7 +560,7 @@ object AuditingSettingsDecoder extends Logging { } private object DeprecatedAuditSettingsDecoder { - def instance: Decoder[Option[AuditingTool.AuditSettings]] = Decoder.instance { c => + def instance(esEnv: EsEnv): Decoder[Option[AuditingTool.AuditSettings]] = Decoder.instance { c => whenEnabled(c) { for { auditIndexTemplate <- decodeOptionalSetting[RorAuditIndexTemplate](c)("index_template", fallbackKey = "audit_index_template") @@ -575,7 +577,8 @@ object AuditingSettingsDecoder extends Logging { auditCluster = remoteAuditCluster.getOrElse(EsIndexBasedSink.default.auditCluster), ) ) - ) + ), + esEnv.esNodeSettings ) } } diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/GlobalStaticSettingsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/GlobalStaticSettingsDecoder.scala index a360b06499..6b1a708fa7 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/GlobalStaticSettingsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/GlobalStaticSettingsDecoder.scala @@ -17,11 +17,11 @@ package tech.beshu.ror.accesscontrol.factory.decoders import io.circe.Decoder -import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, RorConfigurationIndex} +import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, RorSettingsIndex} import tech.beshu.ror.accesscontrol.factory.GlobalSettings import tech.beshu.ror.accesscontrol.factory.GlobalSettings.FlsEngine -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers.optionalDecoder @@ -29,7 +29,7 @@ object GlobalStaticSettingsDecoder { private val globalSettingsSectionName = "global_settings" - def instance(rorConfigurationIndex: RorConfigurationIndex): Decoder[GlobalSettings] = { + def instance(settingsIndex: RorSettingsIndex): Decoder[GlobalSettings] = { for { showBasicAuthPrompt <- decoderFor[Boolean]("prompt_for_basic_auth") forbiddenRequestMessage <- decoderFor[String]("response_if_req_forbidden") @@ -40,7 +40,7 @@ object GlobalStaticSettingsDecoder { showBasicAuthPrompt.getOrElse(false), forbiddenRequestMessage.getOrElse(GlobalSettings.defaultForbiddenRequestMessage), flsEngine.getOrElse(GlobalSettings.FlsEngine.ESWithLucene), - rorConfigurationIndex, + settingsIndex, userIdCaseSensitivity.getOrElse(CaseSensitivity.Enabled), usersDefinitionDuplicateUsernamesValidationEnabled.getOrElse(true) ) diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/common.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/common.scala index 57f2de6b51..6c334da301 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/common.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/common.scala @@ -32,9 +32,9 @@ import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.Json.ResolvableJsonRepresentation import tech.beshu.ror.accesscontrol.domain.User.UserIdPattern import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, ValueLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, ValueLevelCreationError} import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.accesscontrol.utils.SyncDecoderCreator import tech.beshu.ror.implicits.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/DefinitionsBaseDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/DefinitionsBaseDecoder.scala index f97bd373c4..0693201359 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/DefinitionsBaseDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/DefinitionsBaseDecoder.scala @@ -17,8 +17,8 @@ package tech.beshu.ror.accesscontrol.factory.decoders.definitions import cats.Applicative -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions.Item import tech.beshu.ror.accesscontrol.utils.ADecoder import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthenticationServicesDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthenticationServicesDecoder.scala index 8ce212d24d..4c45a78378 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthenticationServicesDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthenticationServicesDecoder.scala @@ -23,9 +23,9 @@ import io.lemonlabs.uri.Url import tech.beshu.ror.accesscontrol.blocks.definitions.* import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.accesscontrol.utils.{ADecoder, SyncDecoder, SyncDecoderCreator} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthorizationServicesDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthorizationServicesDecoder.scala index 749331a056..4db45aec40 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthorizationServicesDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ExternalAuthorizationServicesDecoder.scala @@ -24,8 +24,8 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.HttpExternalAuthorization import tech.beshu.ror.accesscontrol.blocks.definitions.* import tech.beshu.ror.accesscontrol.domain.Header import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, Reason} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, Reason} import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.accesscontrol.utils.{ADecoder, SyncDecoder, SyncDecoderCreator} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ImpersonationDefinitionsDecoderCreator.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ImpersonationDefinitionsDecoderCreator.scala index a6ab809fd7..11c98029a5 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ImpersonationDefinitionsDecoderCreator.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ImpersonationDefinitionsDecoderCreator.scala @@ -26,8 +26,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.Rule import tech.beshu.ror.accesscontrol.domain.User.UserIdPattern import tech.beshu.ror.accesscontrol.domain.UserIdPatterns import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.ruleDecoders import tech.beshu.ror.accesscontrol.utils.CirceOps.{ACursorOps, DecoderHelpers, DecodingFailureOps} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/JwtDefinitionsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/JwtDefinitionsDecoder.scala index e178cb3893..b9d1d7a280 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/JwtDefinitionsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/JwtDefinitionsDecoder.scala @@ -23,9 +23,9 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationSe import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariableCreator import tech.beshu.ror.accesscontrol.domain.{AuthorizationTokenDef, Header, Jwt} import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.definitions.ExternalAuthenticationServicesDecoder.jwtExternalAuthenticationServiceDecoder import tech.beshu.ror.accesscontrol.utils.CirceOps.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/LdapServicesDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/LdapServicesDecoder.scala index 84ee7dd2da..f72c832ed7 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/LdapServicesDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/LdapServicesDecoder.scala @@ -36,9 +36,9 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.User import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode.* import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserSearchFilterConfig.UserIdAttribute import tech.beshu.ror.accesscontrol.domain.PlainTextSecret -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.utils.* import tech.beshu.ror.accesscontrol.utils.CirceOps.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ProxyAuthDefinitionsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ProxyAuthDefinitionsDecoder.scala index fa6eae1313..e5076d735a 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ProxyAuthDefinitionsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/ProxyAuthDefinitionsDecoder.scala @@ -20,8 +20,8 @@ import cats.Id import io.circe.Decoder import tech.beshu.ror.accesscontrol.blocks.definitions.ProxyAuth import tech.beshu.ror.accesscontrol.domain.Header -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.accesscontrol.utils.{ADecoder, SyncDecoder} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/RorKbnDefinitionsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/RorKbnDefinitionsDecoder.scala index 46de7ac75e..ecadc48d32 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/RorKbnDefinitionsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/RorKbnDefinitionsDecoder.scala @@ -22,9 +22,9 @@ import io.circe.{Decoder, HCursor} import tech.beshu.ror.accesscontrol.blocks.definitions.RorKbnDef import tech.beshu.ror.accesscontrol.blocks.definitions.RorKbnDef.{Name, SignatureCheckMethod} import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariableCreator -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers import tech.beshu.ror.accesscontrol.utils.CirceOps.DecodingFailureOps.fromError import tech.beshu.ror.accesscontrol.utils.CryptoOps.keyStringToPublicKey diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/UsersDefinitionsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/UsersDefinitionsDecoder.scala index 8925882778..979bc430da 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/UsersDefinitionsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/UsersDefinitionsDecoder.scala @@ -34,9 +34,9 @@ import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.User.UserIdPattern import tech.beshu.ror.accesscontrol.domain.{Group, GroupIdLike, GroupName, UserIdPatterns} import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, ValueLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, ValueLevelCreationError} import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.ruleDecoders.{usersDefinitionsAllowedRulesDecoderBy, withUserIdParamsCheck} import tech.beshu.ror.accesscontrol.factory.decoders.rules.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/VariableTransformationAliasesDefinitionsDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/VariableTransformationAliasesDefinitionsDecoder.scala index 87b11fca5d..7f36f49f06 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/VariableTransformationAliasesDefinitionsDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/definitions/VariableTransformationAliasesDefinitionsDecoder.scala @@ -24,7 +24,7 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.VariableTransformationAli import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler.CompilationError import tech.beshu.ror.accesscontrol.blocks.variables.transformation.domain.{FunctionAlias, FunctionName} import tech.beshu.ror.accesscontrol.blocks.variables.transformation.{SupportedVariablesFunctions, TransformationCompiler} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, Reason} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, Reason} import tech.beshu.ror.accesscontrol.utils.CirceOps.DecodingFailureOps import tech.beshu.ror.accesscontrol.utils.{ADecoder, SyncDecoder, SyncDecoderCreator} import tech.beshu.ror.implicits.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/ruleDecoders.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/ruleDecoders.scala index 2fe4cdf6c3..4c19953c11 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/ruleDecoders.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/ruleDecoders.scala @@ -34,7 +34,7 @@ import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVa import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, GroupsLogic, UserIdPatterns} import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.definitions.{Definitions, DefinitionsPack} import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleDecoder import tech.beshu.ror.accesscontrol.factory.decoders.rules.auth.* @@ -44,7 +44,7 @@ import tech.beshu.ror.accesscontrol.factory.decoders.rules.http.* import tech.beshu.ror.accesscontrol.factory.decoders.rules.kibana.* import tech.beshu.ror.accesscontrol.factory.decoders.rules.transport.* import tech.beshu.ror.accesscontrol.matchers.GenericPatternMatcher -import tech.beshu.ror.configuration.EnvironmentConfig +import tech.beshu.ror.SystemContext import tech.beshu.ror.implicits.* object ruleDecoders { @@ -53,10 +53,10 @@ object ruleDecoders { definitions: DefinitionsPack, globalSettings: GlobalSettings, mocksProvider: MocksProvider) - (implicit environmentConfig: EnvironmentConfig): Option[RuleDecoder[Rule]] = { + (implicit systemContext: SystemContext): Option[RuleDecoder[Rule]] = { val variableCreator = new RuntimeResolvableVariableCreator( TransformationCompiler.withAliases( - environmentConfig.variablesFunctions, + systemContext.variablesFunctions, definitions.variableTransformationAliases.items.map(_.alias) ) ) @@ -87,17 +87,17 @@ object ruleDecoders { case HeadersAndRule.DeprecatedName.name => Some(new HeadersAndRuleDecoder()(HeadersAndRule.DeprecatedName)) case HeadersOrRule.Name.name => Some(HeadersOrRuleDecoder) case HostsRule.Name.name => Some(new HostsRuleDecoder(variableCreator)) - case IndicesRule.Name.name => Some(new IndicesRuleDecoders(variableCreator, environmentConfig.uniqueIdentifierGenerator)) - case KibanaUserDataRule.Name.name => Some(new KibanaUserDataRuleDecoder(globalSettings.configurationIndex, variableCreator)(environmentConfig.jsCompiler)) - case KibanaAccessRule.Name.name => Some(new KibanaAccessRuleDecoder(globalSettings.configurationIndex)) - case KibanaHideAppsRule.Name.name => Some(new KibanaHideAppsRuleDecoder()(environmentConfig.jsCompiler)) + case IndicesRule.Name.name => Some(new IndicesRuleDecoders(variableCreator, systemContext.uniqueIdentifierGenerator)) + case KibanaUserDataRule.Name.name => Some(new KibanaUserDataRuleDecoder(globalSettings.settingsIndex, variableCreator)(systemContext.jsCompiler)) + case KibanaAccessRule.Name.name => Some(new KibanaAccessRuleDecoder(globalSettings.settingsIndex)) + case KibanaHideAppsRule.Name.name => Some(new KibanaHideAppsRuleDecoder()(systemContext.jsCompiler)) case KibanaIndexRule.Name.name => Some(new KibanaIndexRuleDecoder(variableCreator)) case KibanaTemplateIndexRule.Name.name => Some(new KibanaTemplateIndexRuleDecoder(variableCreator)) case LocalHostsRule.Name.name => Some(new LocalHostsRuleDecoder(variableCreator)) case MaxBodyLengthRule.Name.name => Some(MaxBodyLengthRuleDecoder) case MethodsRule.Name.name => Some(MethodsRuleDecoder) case RepositoriesRule.Name.name => Some(new RepositoriesRuleDecoder(variableCreator)) - case SessionMaxIdleRule.Name.name => Some(new SessionMaxIdleRuleDecoder(globalSettings)(environmentConfig.clock, environmentConfig.uuidProvider)) + case SessionMaxIdleRule.Name.name => Some(new SessionMaxIdleRuleDecoder(globalSettings)(systemContext.clock, systemContext.uuidProvider)) case SnapshotsRule.Name.name => Some(new SnapshotsRuleDecoder(variableCreator)) case UriRegexRule.Name.name => Some(new UriRegexRuleDecoder(variableCreator)) case UsersRule.Name.name => Some(new UsersRuleDecoder(globalSettings, variableCreator)) diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/RuleBaseDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/RuleBaseDecoder.scala index 0962fed2e8..f7463843ca 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/RuleBaseDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/RuleBaseDecoder.scala @@ -22,8 +22,8 @@ import io.circe.{ACursor, Decoder, HCursor} import tech.beshu.ror.accesscontrol.blocks.Block.RuleDefinition import tech.beshu.ror.accesscontrol.blocks.rules.Rule import tech.beshu.ror.accesscontrol.blocks.rules.Rule.* -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleDecoder.DecodingContext import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.implicits.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/AuthKeyRulesDecoders.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/AuthKeyRulesDecoders.scala index 6778237903..86163f3fc4 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/AuthKeyRulesDecoders.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/AuthKeyRulesDecoders.scala @@ -26,8 +26,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeyUnixRule.UnixHashed import tech.beshu.ror.accesscontrol.blocks.rules.auth.base.BasicAuthenticationRule import tech.beshu.ror.accesscontrol.domain.{Credentials, PlainTextSecret, User} import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions import tech.beshu.ror.accesscontrol.factory.decoders.rules.OptionalImpersonatorDefinitionOps import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ExternalAuthenticationRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ExternalAuthenticationRuleDecoder.scala index 8b54d931cf..6cc6dc3235 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ExternalAuthenticationRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ExternalAuthenticationRuleDecoder.scala @@ -23,9 +23,9 @@ import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider import tech.beshu.ror.accesscontrol.blocks.rules.auth.ExternalAuthenticationRule import tech.beshu.ror.accesscontrol.blocks.rules.auth.ExternalAuthenticationRule.Settings import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.definitions.ExternalAuthenticationServicesDecoder.* import tech.beshu.ror.accesscontrol.factory.decoders.definitions.{Definitions, ExternalAuthenticationServicesDecoder} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ExternalAuthorizationRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ExternalAuthorizationRuleDecoder.scala index 900d8cb39b..f8fc3bb7f3 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ExternalAuthorizationRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ExternalAuthorizationRuleDecoder.scala @@ -24,9 +24,9 @@ import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider import tech.beshu.ror.accesscontrol.blocks.rules.auth.ExternalAuthorizationRule import tech.beshu.ror.accesscontrol.domain.{GroupsLogic, User} import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions import tech.beshu.ror.accesscontrol.factory.decoders.definitions.ExternalAuthorizationServicesDecoder.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/JwtAuthRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/JwtAuthRuleDecoder.scala index 35d45c592e..0c09da0705 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/JwtAuthRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/JwtAuthRuleDecoder.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.JwtDef import tech.beshu.ror.accesscontrol.blocks.rules.auth.JwtAuthRule import tech.beshu.ror.accesscontrol.blocks.rules.auth.JwtAuthRule.Groups import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions import tech.beshu.ror.accesscontrol.factory.decoders.definitions.JwtDefinitionsDecoder.* import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/LdapRulesDecoders.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/LdapRulesDecoders.scala index cacac99013..95f7e57d16 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/LdapRulesDecoders.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/LdapRulesDecoders.scala @@ -26,9 +26,9 @@ import tech.beshu.ror.accesscontrol.blocks.rules.Rule.RuleName import tech.beshu.ror.accesscontrol.blocks.rules.auth.{LdapAuthRule, LdapAuthenticationRule, LdapAuthorizationRule} import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, GroupsLogic} import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.definitions.LdapServicesDecoder.nameDecoder import tech.beshu.ror.accesscontrol.factory.decoders.definitions.{Definitions, LdapServicesDecoder} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ProxyAuthRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ProxyAuthRuleDecoder.scala index 74cefa954d..68588b805c 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ProxyAuthRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/ProxyAuthRuleDecoder.scala @@ -23,8 +23,8 @@ import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider import tech.beshu.ror.accesscontrol.blocks.rules.auth.ProxyAuthRule import tech.beshu.ror.accesscontrol.domain.{Header, User} import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.common.userIdDecoder import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions import tech.beshu.ror.accesscontrol.factory.decoders.definitions.ProxyAuthDefinitionsDecoder.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/RorKbnRulesDecoders.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/RorKbnRulesDecoders.scala index c79c91a551..8f9322301a 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/RorKbnRulesDecoders.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/RorKbnRulesDecoders.scala @@ -26,8 +26,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.auth.{RorKbnAuthRule, RorKbnAut import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupIdPattern import tech.beshu.ror.accesscontrol.domain.{GroupIds, GroupsLogic} import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions import tech.beshu.ror.accesscontrol.factory.decoders.definitions.RorKbnDefinitionsDecoder.* import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/BaseGroupsRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/BaseGroupsRuleDecoder.scala index 5a0b4dc4e8..9fc3fb2d4b 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/BaseGroupsRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/BaseGroupsRuleDecoder.scala @@ -30,8 +30,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.auth.base.BaseGroupsRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolvableVariable, RuntimeResolvableGroupsLogic, RuntimeResolvableVariableCreator} import tech.beshu.ror.accesscontrol.domain.GroupsLogic.{NegativeGroupsLogic, PositiveGroupsLogic} import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, GroupIdLike, GroupsLogic} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions import tech.beshu.ror.accesscontrol.factory.decoders.rules.auth.groups.BaseGroupsRuleDecoder.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/GroupsLogicRepresentationDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/GroupsLogicRepresentationDecoder.scala index 098c963ba3..f192bb575a 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/GroupsLogicRepresentationDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/GroupsLogicRepresentationDecoder.scala @@ -21,8 +21,8 @@ import io.circe.{ACursor, Decoder, FailedCursor} import tech.beshu.ror.accesscontrol.blocks.rules.Rule import tech.beshu.ror.accesscontrol.blocks.rules.Rule.RuleName import tech.beshu.ror.accesscontrol.blocks.rules.auth.* -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.auth.groups.GroupsLogicRepresentationDecoder.GroupsLogicDecodingResult import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.accesscontrol.utils.{SyncDecoder, SyncDecoderCreator} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/GroupsRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/GroupsRuleDecoder.scala index 97885fd181..35e2630693 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/GroupsRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/auth/groups/GroupsRuleDecoder.scala @@ -25,8 +25,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.auth.base.BaseGroupsRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariableCreator import tech.beshu.ror.accesscontrol.domain.GroupsLogic import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.definitions.Definitions import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleDecoder diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/DataStreamsRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/DataStreamsRuleDecoder.scala index 4242e272a3..f533a3d960 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/DataStreamsRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/DataStreamsRuleDecoder.scala @@ -26,8 +26,8 @@ import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolva import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariable.Convertible import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolvableVariable, RuntimeResolvableVariableCreator} import tech.beshu.ror.accesscontrol.domain.DataStreamName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.factory.decoders.rules.elasticsearch.DataStreamsDecodersHelper.* import tech.beshu.ror.accesscontrol.orders.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/FieldsRuleLikeDecoderHelperBase.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/FieldsRuleLikeDecoderHelperBase.scala index f9c24098ac..756167525b 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/FieldsRuleLikeDecoderHelperBase.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/FieldsRuleLikeDecoderHelperBase.scala @@ -20,9 +20,9 @@ import eu.timepit.refined.types.string.NonEmptyString import io.circe.Decoder import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariable.Convertible import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariableCreator -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.implicits.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/IndicesRulesDecoders.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/IndicesRulesDecoders.scala index 7d8b512436..e26ce05857 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/IndicesRulesDecoders.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/IndicesRulesDecoders.scala @@ -25,8 +25,8 @@ import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolva import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariable.Convertible import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolvableVariable, RuntimeResolvableVariableCreator} import tech.beshu.ror.accesscontrol.domain.ClusterIndexName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.factory.decoders.rules.elasticsearch.IndicesDecodersHelper.* import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/RepositoriesRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/RepositoriesRuleDecoder.scala index a4a0fa9861..bbe89fac5b 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/RepositoriesRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/RepositoriesRuleDecoder.scala @@ -25,8 +25,8 @@ import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolva import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariable.Convertible import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolvableVariable, RuntimeResolvableVariableCreator} import tech.beshu.ror.accesscontrol.domain.RepositoryName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.factory.decoders.rules.elasticsearch.RepositoriesDecodersHelper.* import tech.beshu.ror.accesscontrol.orders.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/SnapshotsRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/SnapshotsRuleDecoder.scala index 3dd249c1cd..636865bd36 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/SnapshotsRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/elasticsearch/SnapshotsRuleDecoder.scala @@ -25,8 +25,8 @@ import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolva import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariable.Convertible import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolvableVariable, RuntimeResolvableVariableCreator} import tech.beshu.ror.accesscontrol.domain.SnapshotName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.factory.decoders.rules.elasticsearch.SnapshotDecodersHelper.* import tech.beshu.ror.accesscontrol.orders.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/MaxBodyLengthRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/MaxBodyLengthRuleDecoder.scala index 2bf2070ae0..cf1109bb62 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/MaxBodyLengthRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/MaxBodyLengthRuleDecoder.scala @@ -21,8 +21,8 @@ import squants.information.Bytes import tech.beshu.ror.accesscontrol.blocks.Block.RuleDefinition import tech.beshu.ror.accesscontrol.blocks.rules.http.MaxBodyLengthRule import tech.beshu.ror.accesscontrol.blocks.rules.http.MaxBodyLengthRule.Settings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderOps import tech.beshu.ror.implicits.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/MethodsRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/MethodsRuleDecoder.scala index fb188d90d2..f582a60022 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/MethodsRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/MethodsRuleDecoder.scala @@ -20,8 +20,8 @@ import io.circe.Decoder import tech.beshu.ror.accesscontrol.blocks.Block.RuleDefinition import tech.beshu.ror.accesscontrol.blocks.rules.http.MethodsRule import tech.beshu.ror.accesscontrol.blocks.rules.http.MethodsRule.Settings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.factory.decoders.rules.http.MethodsRuleDecoderHelper.methodDecoder import tech.beshu.ror.accesscontrol.orders.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/SessionMaxIdleRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/SessionMaxIdleRuleDecoder.scala index 36b00882d3..bb3d3ffe39 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/SessionMaxIdleRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/SessionMaxIdleRuleDecoder.scala @@ -21,7 +21,7 @@ import tech.beshu.ror.accesscontrol.blocks.Block.RuleDefinition import tech.beshu.ror.accesscontrol.blocks.rules.http.SessionMaxIdleRule import tech.beshu.ror.accesscontrol.blocks.rules.http.SessionMaxIdleRule.Settings import tech.beshu.ror.accesscontrol.factory.GlobalSettings -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.common import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.utils.CirceOps.* diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/UriRegexRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/UriRegexRuleDecoder.scala index dc23a579bf..57dad83c31 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/UriRegexRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/http/UriRegexRuleDecoder.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.http.UriRegexRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariable.Convertible import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariable.Convertible.ConvertError import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolvableVariable, RuntimeResolvableVariableCreator} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.orders.patternOrder import tech.beshu.ror.accesscontrol.utils.CirceOps.{DecoderHelpers, DecoderOps} diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/kibana/KibanaRulesDecoders.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/kibana/KibanaRulesDecoders.scala index 9c8b3fccdd..ee6d769756 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/kibana/KibanaRulesDecoders.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/kibana/KibanaRulesDecoders.scala @@ -22,7 +22,7 @@ import tech.beshu.ror.accesscontrol.blocks.Block.RuleDefinition import tech.beshu.ror.accesscontrol.blocks.rules.kibana.KibanaHideAppsRule.Settings import tech.beshu.ror.accesscontrol.blocks.rules.kibana.{KibanaAccessRule, KibanaHideAppsRule, KibanaIndexRule, KibanaTemplateIndexRule} import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeResolvableVariableCreator, RuntimeSingleResolvableVariable} -import tech.beshu.ror.accesscontrol.domain.{KibanaAccess, KibanaApp, KibanaIndexName, RorConfigurationIndex} +import tech.beshu.ror.accesscontrol.domain.{KibanaAccess, KibanaApp, KibanaIndexName, RorSettingsIndex} import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.utils.CirceOps.* @@ -66,12 +66,12 @@ class KibanaTemplateIndexRuleDecoder(variableCreator: RuntimeResolvableVariableC } } -class KibanaAccessRuleDecoder(rorIndexNameConfiguration: RorConfigurationIndex) +class KibanaAccessRuleDecoder(rorIndexName: RorSettingsIndex) extends RuleBaseDecoderWithoutAssociatedFields[KibanaAccessRule] { override protected def decoder: Decoder[RuleDefinition[KibanaAccessRule]] = { Decoder[KibanaAccess] - .map(KibanaAccessRule.Settings(_, rorIndexNameConfiguration)) + .map(KibanaAccessRule.Settings(_, rorIndexName)) .map(settings => new KibanaAccessRule(settings)) .map(RuleDefinition.create(_)) .decoder diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/kibana/KibanaUserDataRuleDecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/kibana/KibanaUserDataRuleDecoder.scala index d1970219b2..f0e0ed79e0 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/kibana/KibanaUserDataRuleDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/factory/decoders/rules/kibana/KibanaUserDataRuleDecoder.scala @@ -27,9 +27,9 @@ import tech.beshu.ror.accesscontrol.domain.* import tech.beshu.ror.accesscontrol.domain.Json.ResolvableJsonRepresentation import tech.beshu.ror.accesscontrol.domain.KibanaAllowedApiPath.* import tech.beshu.ror.accesscontrol.domain.KibanaAllowedApiPath.AllowedHttpMethod.HttpMethod -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{RulesLevelCreationError, ValueLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{RulesLevelCreationError, ValueLevelCreationError} import tech.beshu.ror.accesscontrol.factory.decoders.common.* import tech.beshu.ror.accesscontrol.factory.decoders.rules.RuleBaseDecoder.RuleBaseDecoderWithoutAssociatedFields import tech.beshu.ror.accesscontrol.utils.CirceOps.* @@ -39,7 +39,7 @@ import tech.beshu.ror.utils.js.JsCompiler import scala.util.{Failure, Success} -class KibanaUserDataRuleDecoder(configurationIndex: RorConfigurationIndex, +class KibanaUserDataRuleDecoder(settingsIndex: RorSettingsIndex, variableCreator: RuntimeResolvableVariableCreator) (implicit jsCompiler: JsCompiler) extends RuleBaseDecoderWithoutAssociatedFields[KibanaUserDataRule] @@ -64,14 +64,12 @@ class KibanaUserDataRuleDecoder(configurationIndex: RorConfigurationIndex, } } yield new KibanaUserDataRule(KibanaUserDataRule.Settings( access = access, - kibanaIndex = kibanaIndex.getOrElse( - RuntimeSingleResolvableVariable.AlreadyResolved(ClusterIndexName.Local.kibanaDefault - )), + kibanaIndex = kibanaIndex.getOrElse(RuntimeSingleResolvableVariable.AlreadyResolved(KibanaIndexName.default)), kibanaTemplateIndex = kibanaTemplateIndex, appsToHide = appsToHide.getOrElse(Set.empty), allowedApiPaths = allowedApiPaths.getOrElse(Set.empty), metadata = metadataResolvableJsonRepresentation, - rorIndex = configurationIndex + rorIndex = settingsIndex )) } .map(RuleDefinition.create[KibanaUserDataRule](_)) diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/logging/AccessControlListLoggingDecorator.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/logging/AccessControlListLoggingDecorator.scala index 05a95a488e..3b88b01b3f 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/logging/AccessControlListLoggingDecorator.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/logging/AccessControlListLoggingDecorator.scala @@ -30,7 +30,6 @@ import tech.beshu.ror.accesscontrol.blocks.{Block, BlockContext, BlockContextUpd import tech.beshu.ror.accesscontrol.domain.Header import tech.beshu.ror.accesscontrol.logging.ResponseContext.* import tech.beshu.ror.accesscontrol.request.RequestContext -import tech.beshu.ror.audit.AuditEnvironmentContext import tech.beshu.ror.constants import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.TaskOps.* @@ -40,7 +39,6 @@ import scala.util.{Failure, Success} class AccessControlListLoggingDecorator(val underlying: AccessControlList, auditingTool: Option[AuditingTool]) (implicit loggingContext: LoggingContext, - auditEnvironmentContext: AuditEnvironmentContext, scheduler: Scheduler) extends AccessControlList with Logging { @@ -111,7 +109,7 @@ class AccessControlListLoggingDecorator(val underlying: AccessControlList, case None | Some(Block.Audit.Enabled) => auditingTool.foreach { _ - .audit(responseContext, auditEnvironmentContext) + .audit(responseContext) .runAsync { case Right(_) => case Left(ex) => diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/ADecoder.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/ADecoder.scala index 5f8e104692..34500a3f48 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/ADecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/ADecoder.scala @@ -20,8 +20,8 @@ import cats.implicits.* import cats.{Functor, Id} import io.circe.* import monix.eval.Task -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason import tech.beshu.ror.accesscontrol.utils.CirceOps.* import tech.beshu.ror.utils.yaml.YamlOps diff --git a/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/CirceOps.scala b/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/CirceOps.scala index d7d2d53c4e..b8b49cd950 100644 --- a/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/CirceOps.scala +++ b/core/src/main/scala/tech/beshu/ror/accesscontrol/utils/CirceOps.scala @@ -27,9 +27,9 @@ import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVa import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeResolvableVariableCreator.CreationError import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeSingleResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolvableVariable, RuntimeResolvableVariableCreator, RuntimeSingleResolvableVariable} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{Reason, ValueLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{Reason, ValueLevelCreationError} import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers.FieldListResult.* import tech.beshu.ror.implicits.* diff --git a/core/src/main/scala/tech/beshu/ror/api/AuthMockApi.scala b/core/src/main/scala/tech/beshu/ror/api/AuthMockApi.scala index fd8aed1fa2..56f49b2183 100644 --- a/core/src/main/scala/tech/beshu/ror/api/AuthMockApi.scala +++ b/core/src/main/scala/tech/beshu/ror/api/AuthMockApi.scala @@ -30,9 +30,10 @@ import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.{ExternalAuthenti import tech.beshu.ror.accesscontrol.blocks.mocks.{AuthServicesMocks, MocksProvider} import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.{Group, GroupName, RequestId, User} -import tech.beshu.ror.boot.RorInstance.{IndexConfigUpdateError, TestConfig} +import tech.beshu.ror.accesscontrol.factory.RorDependencies +import tech.beshu.ror.boot.RorInstance.{IndexSettingsUpdateError, TestSettings} import tech.beshu.ror.boot.{RorInstance, RorSchedulers} -import tech.beshu.ror.configuration.RorConfig +import tech.beshu.ror.implicits.* import tech.beshu.ror.syntax.* import tech.beshu.ror.utils.CirceOps.CirceErrorOps @@ -57,7 +58,7 @@ class AuthMockApi(rorInstance: RorInstance) private def provideAuthMock() (implicit requestId: RequestId): Task[AuthMockResponse] = { - withRorConfigAuthServices( + withRorSettingsAuthServices( action = readCurrentAuthMocks, onNotSet = AuthMockResponse.ProvideAuthMock.NotConfigured.apply, onInvalidated = AuthMockResponse.ProvideAuthMock.Invalidated.apply @@ -65,7 +66,7 @@ class AuthMockApi(rorInstance: RorInstance) .map(_.merge) } - private def readCurrentAuthMocks(services: RorConfig.Services) + private def readCurrentAuthMocks(services: RorDependencies.Services) (implicit requestId: RequestId): AuthMockResponse.ProvideAuthMock.CurrentAuthMocks = { val ldaps = services.ldaps.map { serviceId => toAuthMockService(serviceId, rorInstance.mocksProvider.ldapServiceWith(serviceId)) @@ -99,24 +100,24 @@ class AuthMockApi(rorInstance: RorInstance) } private def readCurrentAuthServices() - (implicit requestId: RequestId): EitherT[Task, AuthMockResponse, RorConfig.Services] = { - EitherT(withRorConfigAuthServices( + (implicit requestId: RequestId): EitherT[Task, AuthMockResponse, RorDependencies.Services] = { + EitherT(withRorSettingsAuthServices( action = identity, onNotSet = AuthMockResponse.UpdateAuthMock.NotConfigured.apply, onInvalidated = AuthMockResponse.UpdateAuthMock.Invalidated.apply )) } - private def withRorConfigAuthServices[A, B](action: RorConfig.Services => B, + private def withRorSettingsAuthServices[A, B](action: RorDependencies.Services => B, onNotSet: String => A, onInvalidated: String => A) (implicit requestId: RequestId): Task[Either[A, B]] = { - rorInstance.currentTestConfig().map { - case TestConfig.NotSet => + rorInstance.currentTestSettings().map { + case TestSettings.NotSet => Left(onNotSet(testSettingsNotConfiguredMessage)) - case TestConfig.Present(config, _, _, _) => - Right(action(config.services)) - case _:TestConfig.Invalidated => + case TestSettings.Present(_, dependencies, _, _) => + Right(action(dependencies.services)) + case _:TestSettings.Invalidated => Left(onInvalidated(testSettingsInvalidatedMessage)) } } @@ -126,7 +127,7 @@ class AuthMockApi(rorInstance: RorInstance) private val testSettingsNotConfiguredMessage = "ROR Test settings are not configured. To use Auth Services Mock ROR has to have Test settings active." private def validateAuthMocks(updateRequest: UpdateMocksRequest, - services: RorConfig.Services): EitherT[Task, AuthMockResponse, Unit] = { + services: RorDependencies.Services): EitherT[Task, AuthMockResponse, Unit] = { updateRequest .services .map { @@ -157,11 +158,11 @@ class AuthMockApi(rorInstance: RorInstance) .map { case Right(()) => Right(UpdateAuthMock.Success("Auth mock updated")) - case Left(IndexConfigUpdateError.TestSettingsNotSet) => + case Left(IndexSettingsUpdateError.TestSettingsNotSet) => Left(AuthMockResponse.UpdateAuthMock.NotConfigured(testSettingsNotConfiguredMessage)) - case Left(IndexConfigUpdateError.TestSettingsInvalidated) => + case Left(IndexSettingsUpdateError.TestSettingsInvalidated) => Left(AuthMockResponse.UpdateAuthMock.Invalidated(testSettingsInvalidatedMessage)) - case Left(IndexConfigUpdateError.IndexConfigSavingError(error)) => + case Left(IndexSettingsUpdateError.IndexSettingsSavingError(error)) => Left(AuthMockResponse.UpdateAuthMock.Failed(s"Cannot save auth services mocks: ${error.show}")) } } diff --git a/core/src/main/scala/tech/beshu/ror/api/ConfigApi.scala b/core/src/main/scala/tech/beshu/ror/api/ConfigApi.scala deleted file mode 100644 index 22b9f2c9dc..0000000000 --- a/core/src/main/scala/tech/beshu/ror/api/ConfigApi.scala +++ /dev/null @@ -1,211 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.api - -import cats.Show -import cats.data.EitherT -import cats.implicits.* -import io.circe.Decoder -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.domain.{RequestId, RorConfigurationIndex} -import tech.beshu.ror.api.ConfigApi.* -import tech.beshu.ror.api.ConfigApi.ConfigRequest.Type -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.boot.RorInstance.IndexConfigReloadWithUpdateError.{IndexConfigSavingError, ReloadError} -import tech.beshu.ror.boot.RorInstance.{IndexConfigReloadError, RawConfigReloadError} -import tech.beshu.ror.boot.{RorInstance, RorSchedulers} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} -import tech.beshu.ror.configuration.index.IndexConfigError.IndexConfigNotExist -import tech.beshu.ror.configuration.index.{IndexConfigError, IndexConfigManager} -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError.SpecializedError -import tech.beshu.ror.configuration.loader.FileConfigLoader -import tech.beshu.ror.utils.CirceOps.toCirceErrorOps - -class ConfigApi(rorInstance: RorInstance, - indexConfigManager: IndexConfigManager, - fileConfigLoader: FileConfigLoader, - rorConfigurationIndex: RorConfigurationIndex) - (implicit val EnvironmentConfig: EnvironmentConfig) - extends Logging { - - import ConfigApi.Utils.* - import ConfigApi.Utils.decoders.* - - def call(request: ConfigRequest) - (implicit requestId: RequestId): Task[ConfigResponse] = { - val configResponse = request.aType match { - case Type.ForceReload => forceReloadRor() - case Type.ProvideIndexConfig => provideRorIndexConfig() - case Type.ProvideFileConfig => provideRorFileConfig() - case Type.UpdateIndexConfig => updateIndexConfiguration(request.body) - } - configResponse - .executeOn(RorSchedulers.restApiScheduler) - } - - private def forceReloadRor() - (implicit requestId: RequestId): Task[ConfigResponse] = { - rorInstance - .forceReloadFromIndex() - .map { - case Right(_) => - ForceReloadConfig.Success("ReadonlyREST settings were reloaded with success!") - case Left(IndexConfigReloadError.LoadingConfigError(error)) => - ForceReloadConfig.Failure(error.show) - case Left(IndexConfigReloadError.ReloadError(RawConfigReloadError.ConfigUpToDate(_))) => - ForceReloadConfig.Failure("Current settings are already loaded") - case Left(IndexConfigReloadError.ReloadError(RawConfigReloadError.RorInstanceStopped)) => - ForceReloadConfig.Failure("ROR is stopped") - case Left(IndexConfigReloadError.ReloadError(RawConfigReloadError.ReloadingFailed(failure))) => - ForceReloadConfig.Failure(s"Cannot reload new settings: ${failure.message.show}") - } - } - - private def updateIndexConfiguration(body: String) - (implicit requestId: RequestId): Task[ConfigResponse] = { - val result = for { - updateRequest <- EitherT.fromEither[Task](decodeUpdateRequest(body)) - config <- rorConfigFrom(updateRequest.configString) - _ <- forceReloadAndSaveNewConfig(config) - } yield UpdateIndexConfig.Success("updated settings") - - result.value.map(_.merge) - } - - private def provideRorFileConfig(): Task[ConfigResponse] = { - fileConfigLoader - .load() - .map { - case Right(config) => ProvideFileConfig.Config(config.raw) - case Left(error) => ProvideFileConfig.Failure(error.show) - } - } - - private def provideRorIndexConfig(): Task[ConfigResponse] = { - indexConfigManager - .load(rorConfigurationIndex) - .map { - case Right(config) => - ProvideIndexConfig.Config(config.raw) - case Left(SpecializedError(error: IndexConfigNotExist.type)) => - ProvideIndexConfig.ConfigNotFound(Show[IndexConfigError].show(error)) - case Left(error) => - ProvideIndexConfig.Failure(error.show) - } - } - - private def decodeUpdateRequest(payload: String): Either[ConfigResponse.Failure, UpdateConfigRequest] = { - io.circe.parser.decode[UpdateConfigRequest](payload) - .left.map(error => ConfigResponse.Failure.BadRequest(s"JSON body malformed: [${error.getPrettyMessage.show}]")) - } - - private def rorConfigFrom(configString: String): EitherT[Task, ConfigResponse, RawRorConfig] = EitherT { - RawRorConfig - .fromString(configString) - .map(_.left.map(error => UpdateIndexConfig.Failure(error.show))) - } - - private def forceReloadAndSaveNewConfig(config: RawRorConfig) - (implicit requestId: RequestId): EitherT[Task, ConfigResponse, Unit] = { - EitherT(rorInstance.forceReloadAndSave(config)) - .leftMap { - case IndexConfigSavingError(error) => - UpdateIndexConfig.Failure(s"Cannot save new settings: ${error.show}") - case ReloadError(RawConfigReloadError.ConfigUpToDate(_)) => - UpdateIndexConfig.Failure(s"Current settings are already loaded") - case ReloadError(RawConfigReloadError.RorInstanceStopped) => - UpdateIndexConfig.Failure(s"ROR instance is being stopped") - case ReloadError(RawConfigReloadError.ReloadingFailed(failure)) => - UpdateIndexConfig.Failure(s"Cannot reload new settings: ${failure.message.show}") - } - } -} - -object ConfigApi { - - final case class ConfigRequest(aType: ConfigRequest.Type, - body: String) - object ConfigRequest { - sealed trait Type - object Type { - case object ForceReload extends Type - case object ProvideIndexConfig extends Type - case object ProvideFileConfig extends Type - case object UpdateIndexConfig extends Type - } - } - - sealed trait ConfigResponse - object ConfigResponse { - sealed trait ForceReloadConfig extends ConfigResponse - object ForceReloadConfig { - final case class Success(message: String) extends ForceReloadConfig - final case class Failure(message: String) extends ForceReloadConfig - } - - sealed trait ProvideIndexConfig extends ConfigResponse - object ProvideIndexConfig { - final case class Config(rawConfig: String) extends ProvideIndexConfig - final case class ConfigNotFound(message: String) extends ProvideIndexConfig - final case class Failure(message: String) extends ProvideIndexConfig - } - - sealed trait ProvideFileConfig extends ConfigResponse - object ProvideFileConfig { - final case class Config(rawConfig: String) extends ProvideFileConfig - final case class Failure(message: String) extends ProvideFileConfig - } - - sealed trait UpdateIndexConfig extends ConfigResponse - object UpdateIndexConfig { - final case class Success(message: String) extends UpdateIndexConfig - final case class Failure(message: String) extends UpdateIndexConfig - } - - sealed trait Failure extends ConfigResponse - object Failure { - final case class BadRequest(message: String) extends Failure - } - } - - implicit class StatusFromConfigResponse(val response: ConfigResponse) extends AnyVal { - def status: String = response match { - case _: ForceReloadConfig.Success => "ok" - case _: ForceReloadConfig.Failure => "ko" - case _: ProvideIndexConfig.Config => "ok" - case _: ProvideIndexConfig.ConfigNotFound => "empty" - case _: ProvideIndexConfig.Failure => "ko" - case _: ProvideFileConfig.Config => "ok" - case _: ProvideFileConfig.Failure => "ko" - case _: UpdateIndexConfig.Success => "ok" - case _: UpdateIndexConfig.Failure => "ko" - case failure: ConfigResponse.Failure => failure match { - case Failure.BadRequest(_) => "ko" - } - } - } - - private object Utils { - final case class UpdateConfigRequest(configString: String) - - object decoders { - implicit val updateConfigRequestDecoder: Decoder[UpdateConfigRequest] = - Decoder.forProduct1("settings")(UpdateConfigRequest.apply) - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/api/MainSettingsApi.scala b/core/src/main/scala/tech/beshu/ror/api/MainSettingsApi.scala new file mode 100644 index 0000000000..28ae1ac9f0 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/api/MainSettingsApi.scala @@ -0,0 +1,220 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.api + +import cats.Show +import cats.data.EitherT +import cats.implicits.* +import io.circe.Decoder +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.MainSettingsApi.* +import tech.beshu.ror.api.MainSettingsApi.MainSettingsRequest.Type +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.boot.RorInstance.IndexSettingsReloadWithUpdateError.{IndexSettingsSavingError, ReloadError} +import tech.beshu.ror.boot.RorInstance.{IndexSettingsReloadError, RawSettingsReloadError} +import tech.beshu.ror.boot.{RorInstance, RorSchedulers} +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.ror.source.IndexSettingsSource.LoadingError.IndexNotFound +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource.SettingsLoadingError.SourceSpecificError +import tech.beshu.ror.settings.ror.source.{FileSettingsSource, IndexSettingsSource} +import tech.beshu.ror.settings.ror.{MainRorSettings, RawRorSettings, RawRorSettingsYamlParser} +import tech.beshu.ror.utils.CirceOps.toCirceErrorOps + +class MainSettingsApi(rorInstance: RorInstance, + settingsYamlParser: RawRorSettingsYamlParser, + mainSettingsIndexSource: IndexSettingsSource[MainRorSettings], + mainSettingsFileSource: FileSettingsSource[MainRorSettings]) + extends Logging { + + import MainSettingsApi.Utils.* + import MainSettingsApi.Utils.decoders.* + + def call(request: MainSettingsRequest) + (implicit requestId: RequestId): Task[MainSettingsResponse] = { + val settingsResponse = request.aType match { + case Type.ForceReload => forceReloadRor() + case Type.ProvideIndexSettings => provideRorIndexSettings() + case Type.ProvideFileSettings => provideRorFileSettings() + case Type.UpdateIndexSettings => updateRorIndexSettings(request.body) + } + settingsResponse + .executeOn(RorSchedulers.restApiScheduler) + } + + private def forceReloadRor() + (implicit requestId: RequestId): Task[MainSettingsResponse] = { + rorInstance + .forceReloadFromIndex() + .map { + case Right(()) => + ForceReloadMainSettings.Success("ReadonlyREST settings were reloaded with success!") + case Left(IndexSettingsReloadError.IndexLoadingSettingsError(error)) => + ForceReloadMainSettings.Failure(error.show) + case Left(IndexSettingsReloadError.ReloadError(RawSettingsReloadError.SettingsUpToDate(_))) => + ForceReloadMainSettings.Failure("Current settings are already loaded") + case Left(IndexSettingsReloadError.ReloadError(RawSettingsReloadError.RorInstanceStopped)) => + ForceReloadMainSettings.Failure("ROR is stopped") + case Left(IndexSettingsReloadError.ReloadError(RawSettingsReloadError.ReloadingFailed(failure))) => + ForceReloadMainSettings.Failure(s"Cannot reload new settings: ${failure.message.show}") + } + } + + private def updateRorIndexSettings(body: String) + (implicit requestId: RequestId): Task[MainSettingsResponse] = { + val result = for { + updateRequest <- EitherT.fromEither[Task](decodeUpdateRequest(body)) + newRorSettings <- rorMainSettingsFrom(updateRequest.settingsString) + _ <- forceReloadAndSaveNewSettings(newRorSettings) + } yield UpdateIndexMainSettings.Success("updated settings") + + result.value.map(_.merge) + } + + private def provideRorFileSettings(): Task[MainSettingsResponse] = { + mainSettingsFileSource + .load() + .map { + case Right(settings) => ProvideFileMainSettings.MainSettings(settings.rawSettings.rawYaml) + case Left(error) => ProvideFileMainSettings.Failure(error.show) + } + } + + private def provideRorIndexSettings(): Task[MainSettingsResponse] = { + mainSettingsIndexSource + .load() + .map { + case Right(settings) => + ProvideIndexMainSettings.MainSettings(settings.rawSettings.rawYaml) + case Left(SourceSpecificError(error@IndexNotFound)) => + ProvideIndexMainSettings.MainSettingsNotFound(Show[IndexSettingsSource.LoadingError].show(error)) + case Left(error) => + ProvideIndexMainSettings.Failure(error.show) + } + } + + private def decodeUpdateRequest(payload: String): Either[MainSettingsResponse.Failure, UpdateSettingsRequest] = { + io.circe.parser.decode[UpdateSettingsRequest](payload) + .left.map(error => MainSettingsResponse.Failure.BadRequest(s"JSON body malformed: [${error.getPrettyMessage.show}]")) + } + + private def rorMainSettingsFrom(settingsString: String): EitherT[Task, MainSettingsResponse, RawRorSettings] = { + settingsYamlParser + .fromString(settingsString) + .left.map(error => UpdateIndexMainSettings.Failure(error.show): MainSettingsResponse) + .toEitherT[Task] + } + + private def forceReloadAndSaveNewSettings(settings: RawRorSettings) + (implicit requestId: RequestId): EitherT[Task, MainSettingsResponse, Unit] = { + EitherT(rorInstance.forceReloadAndSave(settings)) + .leftMap { + case IndexSettingsSavingError(error) => + UpdateIndexMainSettings.Failure(s"Cannot save new settings: ${error.show}") + case ReloadError(RawSettingsReloadError.SettingsUpToDate(_)) => + UpdateIndexMainSettings.Failure(s"Current settings are already loaded") + case ReloadError(RawSettingsReloadError.RorInstanceStopped) => + UpdateIndexMainSettings.Failure(s"ROR instance is being stopped") + case ReloadError(RawSettingsReloadError.ReloadingFailed(failure)) => + UpdateIndexMainSettings.Failure(s"Cannot reload new settings: ${failure.message.show}") + } + } +} + +object MainSettingsApi { + + final class Creator(settingsYamlParser: RawRorSettingsYamlParser, + mainSettingsIndexSource: IndexSettingsSource[MainRorSettings], + mainSettingsFileSource: FileSettingsSource[MainRorSettings]) { + + def create(rorInstance: RorInstance): MainSettingsApi = { + new MainSettingsApi(rorInstance, settingsYamlParser, mainSettingsIndexSource, mainSettingsFileSource) + } + } + + final case class MainSettingsRequest(aType: MainSettingsRequest.Type, + body: String) + object MainSettingsRequest { + sealed trait Type + object Type { + case object ForceReload extends Type + case object ProvideIndexSettings extends Type + case object ProvideFileSettings extends Type + case object UpdateIndexSettings extends Type + } + } + + sealed trait MainSettingsResponse + object MainSettingsResponse { + sealed trait ForceReloadMainSettings extends MainSettingsResponse + object ForceReloadMainSettings { + final case class Success(message: String) extends ForceReloadMainSettings + final case class Failure(message: String) extends ForceReloadMainSettings + } + + sealed trait ProvideIndexMainSettings extends MainSettingsResponse + object ProvideIndexMainSettings { + final case class MainSettings(rawSettings: String) extends ProvideIndexMainSettings + final case class MainSettingsNotFound(message: String) extends ProvideIndexMainSettings + final case class Failure(message: String) extends ProvideIndexMainSettings + } + + sealed trait ProvideFileMainSettings extends MainSettingsResponse + object ProvideFileMainSettings { + final case class MainSettings(rawSettings: String) extends ProvideFileMainSettings + final case class Failure(message: String) extends ProvideFileMainSettings + } + + sealed trait UpdateIndexMainSettings extends MainSettingsResponse + object UpdateIndexMainSettings { + final case class Success(message: String) extends UpdateIndexMainSettings + final case class Failure(message: String) extends UpdateIndexMainSettings + } + + sealed trait Failure extends MainSettingsResponse + object Failure { + final case class BadRequest(message: String) extends Failure + } + } + + implicit class StatusFromSettingsResponse(val response: MainSettingsResponse) extends AnyVal { + def status: String = response match { + case _: ForceReloadMainSettings.Success => "ok" + case _: ForceReloadMainSettings.Failure => "ko" + case _: ProvideIndexMainSettings.MainSettings => "ok" + case _: ProvideIndexMainSettings.MainSettingsNotFound => "empty" + case _: ProvideIndexMainSettings.Failure => "ko" + case _: ProvideFileMainSettings.MainSettings => "ok" + case _: ProvideFileMainSettings.Failure => "ko" + case _: UpdateIndexMainSettings.Success => "ok" + case _: UpdateIndexMainSettings.Failure => "ko" + case failure: MainSettingsResponse.Failure => failure match { + case Failure.BadRequest(_) => "ko" + } + } + } + + private object Utils { + final case class UpdateSettingsRequest(settingsString: String) + + object decoders { + implicit val updateSettingsRequestDecoder: Decoder[UpdateSettingsRequest] = + Decoder.forProduct1("settings")(UpdateSettingsRequest.apply) + } + } +} diff --git a/core/src/main/scala/tech/beshu/ror/api/TestConfigApi.scala b/core/src/main/scala/tech/beshu/ror/api/TestConfigApi.scala deleted file mode 100644 index 2d72fb66c2..0000000000 --- a/core/src/main/scala/tech/beshu/ror/api/TestConfigApi.scala +++ /dev/null @@ -1,267 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.api - -import cats.data.EitherT -import cats.implicits.* -import io.circe.Decoder -import monix.eval.Task -import tech.beshu.ror.accesscontrol.blocks.ImpersonationWarning -import tech.beshu.ror.accesscontrol.domain.{LoggedUser, RequestId} -import tech.beshu.ror.api.TestConfigApi.TestConfigRequest.Type -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* -import tech.beshu.ror.api.TestConfigApi.{TestConfigRequest, TestConfigResponse} -import tech.beshu.ror.boot.RorInstance.IndexConfigReloadWithUpdateError.{IndexConfigSavingError, ReloadError} -import tech.beshu.ror.boot.RorInstance.{IndexConfigInvalidationError, RawConfigReloadError, TestConfig} -import tech.beshu.ror.boot.{RorInstance, RorSchedulers} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} -import tech.beshu.ror.syntax.* -import tech.beshu.ror.utils.CirceOps.toCirceErrorOps -import tech.beshu.ror.utils.DurationOps.* - -import java.time.Instant -import scala.concurrent.duration.* -import scala.util.Try - -class TestConfigApi(rorInstance: RorInstance) - (implicit environmentConfig: EnvironmentConfig) { - - import tech.beshu.ror.api.TestConfigApi.Utils.* - import tech.beshu.ror.api.TestConfigApi.Utils.decoders.* - - def call(request: RorApiRequest[TestConfigRequest]) - (implicit requestId: RequestId): Task[TestConfigResponse] = { - val testConfigResponse = request.request.aType match { - case Type.ProvideTestConfig => loadCurrentTestConfig() - case Type.UpdateTestConfig => updateTestConfig(request.request.body) - case Type.InvalidateTestConfig => invalidateTestConfig() - case Type.ProvideLocalUsers => provideLocalUsers(request.loggedUser) - } - testConfigResponse - .executeOn(RorSchedulers.restApiScheduler) - } - - private def updateTestConfig(body: String) - (implicit requestId: RequestId): Task[TestConfigResponse] = { - val result = for { - updateRequest <- EitherT.fromEither[Task](decodeUpdateConfigRequest(body)) - rorConfig <- rorTestConfig(updateRequest.configString) - response <- forceReloadTestConfig(rorConfig, updateRequest.ttl) - } yield response - - result.value.map(_.merge) - } - - private def decodeUpdateConfigRequest(payload: String): Either[Failure, UpdateTestConfigRequest] = { - io.circe.parser.decode[UpdateTestConfigRequest](payload) - .left.map(error => TestConfigResponse.Failure.BadRequest(s"JSON body malformed: [${error.getPrettyMessage}]")) - } - - private def rorTestConfig(configString: String): EitherT[Task, TestConfigResponse, RawRorConfig] = EitherT { - RawRorConfig - .fromString(configString) - .map(_.left.map(error => TestConfigResponse.UpdateTestConfig.FailedResponse(error.show))) - } - - private def invalidateTestConfig() - (implicit requestId: RequestId): Task[TestConfigResponse] = { - rorInstance - .invalidateTestConfigEngine() - .map { - case Right(()) => - TestConfigResponse.InvalidateTestConfig.SuccessResponse("ROR Test settings are invalidated") - case Left(IndexConfigInvalidationError.IndexConfigSavingError(error)) => - TestConfigResponse.InvalidateTestConfig.FailedResponse(s"Cannot invalidate test settings: ${error.show}") - } - } - - private def loadCurrentTestConfig() - (implicit requestId: RequestId): Task[TestConfigResponse] = { - rorInstance - .currentTestConfig() - .map { - case TestConfig.NotSet => - TestConfigResponse.ProvideTestConfig.TestSettingsNotConfigured("ROR Test settings are not configured") - case TestConfig.Present(config, rawConfig, configuredTtl, validTo) => - TestConfigResponse.ProvideTestConfig.CurrentTestSettings( - ttl = apiFormat(configuredTtl), - validTo = validTo, - settings = rawConfig, - warnings = config.impersonationWarningsReader.read().map(toWarningDto) - ) - case TestConfig.Invalidated(recentConfig, ttl) => - TestConfigResponse.ProvideTestConfig.TestSettingsInvalidated("ROR Test settings are invalidated", recentConfig, apiFormat(ttl)) - } - } - - private def provideLocalUsers(loggedUser: Option[LoggedUser]) - (implicit requestId: RequestId): Task[TestConfigResponse] = { - rorInstance - .currentTestConfig() - .map { - case TestConfig.NotSet => - TestConfigResponse.ProvideLocalUsers.TestSettingsNotConfigured("ROR Test settings are not configured") - case TestConfig.Present(config, _, _, _) => - val filteredLocalUsers = config.localUsers.users -- loggedUser.map(_.id) - TestConfigResponse.ProvideLocalUsers.SuccessResponse( - users = filteredLocalUsers.map(_.value.value).toList, - unknownUsers = config.localUsers.unknownUsers - ) - case _:TestConfig.Invalidated => - TestConfigResponse.ProvideLocalUsers.TestSettingsInvalidated("ROR Test settings are invalidated") - } - } - - private def forceReloadTestConfig(config: RawRorConfig, - ttl: PositiveFiniteDuration) - (implicit requestId: RequestId): EitherT[Task, TestConfigResponse, TestConfigResponse] = { - EitherT( - rorInstance - .forceReloadTestConfigEngine(config, ttl) - .map { - _ - .map { newTestConfig => - TestConfigResponse.UpdateTestConfig.SuccessResponse( - message = "updated settings", - validTo = newTestConfig.validTo, - warnings = newTestConfig.config.impersonationWarningsReader.read().map(toWarningDto) - ) - } - .leftMap { - case IndexConfigSavingError(error) => - TestConfigResponse.UpdateTestConfig.FailedResponse(s"Cannot reload new settings: ${error.show}") - case ReloadError(RawConfigReloadError.ConfigUpToDate(_)) => - TestConfigResponse.UpdateTestConfig.FailedResponse(s"Current settings are already loaded") - case ReloadError(RawConfigReloadError.RorInstanceStopped) => - TestConfigResponse.UpdateTestConfig.FailedResponse(s"ROR instance is being stopped") - case ReloadError(RawConfigReloadError.ReloadingFailed(failure)) => - TestConfigResponse.UpdateTestConfig.FailedResponse(s"Cannot reload new settings: ${failure.message}") - } - } - ) - } - - private def toWarningDto(warning: ImpersonationWarning): TestConfigResponse.Warning = { - TestConfigResponse.Warning( - blockName = warning.block.value, - ruleName = warning.ruleName.value, - message = warning.message.value, - hint = warning.hint - ) - } -} - -object TestConfigApi { - - final case class TestConfigRequest(aType: TestConfigRequest.Type, - body: String) - - object TestConfigRequest { - sealed trait Type - object Type { - case object ProvideTestConfig extends Type - case object UpdateTestConfig extends Type - case object InvalidateTestConfig extends Type - case object ProvideLocalUsers extends Type - } - } - - sealed trait TestConfigResponse - object TestConfigResponse { - - final case class Warning(blockName: String, - ruleName: String, - message: String, - hint: String) - - sealed trait ProvideTestConfig extends TestConfigResponse - object ProvideTestConfig { - final case class CurrentTestSettings(ttl: FiniteDuration, - validTo: Instant, - settings: RawRorConfig, - warnings: List[Warning]) extends ProvideTestConfig - - final case class TestSettingsNotConfigured(message: String) extends ProvideTestConfig - final case class TestSettingsInvalidated(message: String, - settings: RawRorConfig, - ttl: FiniteDuration) extends ProvideTestConfig - } - - sealed trait UpdateTestConfig extends TestConfigResponse - object UpdateTestConfig { - final case class SuccessResponse(message: String, validTo: Instant, warnings: List[Warning]) extends UpdateTestConfig - final case class FailedResponse(message: String) extends UpdateTestConfig - } - - sealed trait InvalidateTestConfig extends TestConfigResponse - object InvalidateTestConfig { - final case class SuccessResponse(message: String) extends InvalidateTestConfig - final case class FailedResponse(message: String) extends InvalidateTestConfig - } - - sealed trait ProvideLocalUsers extends TestConfigResponse - object ProvideLocalUsers { - final case class SuccessResponse(users: List[String], unknownUsers: Boolean) extends ProvideLocalUsers - final case class TestSettingsNotConfigured(message: String) extends ProvideLocalUsers - final case class TestSettingsInvalidated(message: String) extends ProvideLocalUsers - } - - sealed trait Failure extends TestConfigResponse - object Failure { - final case class BadRequest(message: String) extends Failure - } - } - - implicit class StatusFromTestConfigResponse(val response: TestConfigResponse) extends AnyVal { - def status: String = response match { - case _: ProvideTestConfig.CurrentTestSettings => "TEST_SETTINGS_PRESENT" - case _: ProvideTestConfig.TestSettingsNotConfigured => "TEST_SETTINGS_NOT_CONFIGURED" - case _: ProvideTestConfig.TestSettingsInvalidated => "TEST_SETTINGS_INVALIDATED" - case _: UpdateTestConfig.SuccessResponse => "OK" - case _: UpdateTestConfig.FailedResponse => "FAILED" - case _: InvalidateTestConfig.SuccessResponse => "OK" - case _: InvalidateTestConfig.FailedResponse => "FAILED" - case _: ProvideLocalUsers.SuccessResponse => "OK" - case _: ProvideLocalUsers.TestSettingsNotConfigured => "TEST_SETTINGS_NOT_CONFIGURED" - case _: ProvideLocalUsers.TestSettingsInvalidated => "TEST_SETTINGS_INVALIDATED" - case _: Failure.BadRequest => "FAILED" - } - } - - private object Utils { - final case class UpdateTestConfigRequest(configString: String, - ttl: PositiveFiniteDuration) - - private def parseDuration(value: String): Either[String, PositiveFiniteDuration] = { - Try(Duration(value)) - .toEither - .leftMap(_ =>s"Cannot parse '${value.show}' as duration.") - .flatMap(_.toRefinedPositive) - } - - object decoders { - implicit val durationDecoder: Decoder[PositiveFiniteDuration] = Decoder.decodeString.emap(parseDuration) - - implicit val updateTestConfigRequestDecoder: Decoder[UpdateTestConfigRequest] = - Decoder.forProduct2("settings", "ttl")(UpdateTestConfigRequest.apply) - } - - def apiFormat(duration: PositiveFiniteDuration): FiniteDuration = { - duration.value.toCoarsest - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/api/TestSettingsApi.scala b/core/src/main/scala/tech/beshu/ror/api/TestSettingsApi.scala new file mode 100644 index 0000000000..3e2c8672ca --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/api/TestSettingsApi.scala @@ -0,0 +1,276 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.api + +import cats.data.EitherT +import cats.implicits.* +import io.circe.Decoder +import monix.eval.Task +import tech.beshu.ror.accesscontrol.blocks.ImpersonationWarning +import tech.beshu.ror.accesscontrol.domain.{LoggedUser, RequestId} +import tech.beshu.ror.api.TestSettingsApi.TestSettingsRequest.Type +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* +import tech.beshu.ror.api.TestSettingsApi.{TestSettingsRequest, TestSettingsResponse} +import tech.beshu.ror.boot.RorInstance.IndexSettingsReloadWithUpdateError.{IndexSettingsSavingError, ReloadError} +import tech.beshu.ror.boot.RorInstance.{IndexSettingsInvalidationError, RawSettingsReloadError, TestSettings} +import tech.beshu.ror.boot.{RorInstance, RorSchedulers} +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.ror.{RawRorSettings, RawRorSettingsYamlParser} +import tech.beshu.ror.syntax.* +import tech.beshu.ror.utils.CirceOps.toCirceErrorOps +import tech.beshu.ror.utils.DurationOps.* + +import java.time.Instant +import scala.concurrent.duration.* +import scala.util.Try + +class TestSettingsApi(rorInstance: RorInstance, + settingsYamlParser: RawRorSettingsYamlParser) { + + import tech.beshu.ror.api.TestSettingsApi.Utils.* + import tech.beshu.ror.api.TestSettingsApi.Utils.decoders.* + + def call(request: RorApiRequest[TestSettingsRequest]) + (implicit requestId: RequestId): Task[TestSettingsResponse] = { + val testSettingsResponse = request.request.aType match { + case Type.ProvideTestSettings => loadCurrentTestSettings() + case Type.UpdateTestSettings => updateTestSettings(request.request.body) + case Type.InvalidateTestSettings => invalidateTestSettings() + case Type.ProvideLocalUsers => provideLocalUsers(request.loggedUser) + } + testSettingsResponse + .executeOn(RorSchedulers.restApiScheduler) + } + + private def updateTestSettings(body: String) + (implicit requestId: RequestId): Task[TestSettingsResponse] = { + val result = for { + updateRequest <- EitherT.fromEither[Task](decodeUpdateSettingsRequest(body)) + rorSettings <- rorTestSettingsFrom(updateRequest.settingsString) + response <- forceReloadTestSettings(rorSettings, updateRequest.ttl) + } yield response + + result.value.map(_.merge) + } + + private def decodeUpdateSettingsRequest(payload: String): Either[Failure, UpdateTestSettingsRequest] = { + io.circe.parser.decode[UpdateTestSettingsRequest](payload) + .left.map(error => TestSettingsResponse.Failure.BadRequest(s"JSON body malformed: [${error.getPrettyMessage}]")) + } + + private def rorTestSettingsFrom(settingsString: String): EitherT[Task, TestSettingsResponse, RawRorSettings] = { + settingsYamlParser + .fromString(settingsString) + .left.map(error => TestSettingsResponse.UpdateTestSettings.FailedResponse(error.show): TestSettingsResponse) + .toEitherT[Task] + } + + private def invalidateTestSettings() + (implicit requestId: RequestId): Task[TestSettingsResponse] = { + rorInstance + .invalidateTestSettingsEngine() + .map { + case Right(()) => + TestSettingsResponse.InvalidateTestSettings.SuccessResponse("ROR Test settings are invalidated") + case Left(IndexSettingsInvalidationError.IndexSettingsSavingError(error)) => + TestSettingsResponse.InvalidateTestSettings.FailedResponse(s"Cannot invalidate test settings: ${error.show}") + } + } + + private def loadCurrentTestSettings() + (implicit requestId: RequestId): Task[TestSettingsResponse] = { + rorInstance + .currentTestSettings() + .map { + case TestSettings.NotSet => + TestSettingsResponse.ProvideTestSettings.TestSettingsNotConfigured("ROR Test settings are not configured") + case TestSettings.Present(rawSettings, dependencies, configuredTtl, validTo) => + TestSettingsResponse.ProvideTestSettings.CurrentTestSettings( + ttl = apiFormat(configuredTtl), + validTo = validTo, + settings = rawSettings, + warnings = dependencies.impersonationWarningsReader.read().map(toWarningDto) + ) + case TestSettings.Invalidated(recentConfig, ttl) => + TestSettingsResponse.ProvideTestSettings.TestSettingsInvalidated("ROR Test settings are invalidated", recentConfig, apiFormat(ttl)) + } + } + + private def provideLocalUsers(loggedUser: Option[LoggedUser]) + (implicit requestId: RequestId): Task[TestSettingsResponse] = { + rorInstance + .currentTestSettings() + .map { + case TestSettings.NotSet => + TestSettingsResponse.ProvideLocalUsers.TestSettingsNotConfigured("ROR Test settings are not configured") + case TestSettings.Present(_, dependencies, _, _) => + val filteredLocalUsers = dependencies.localUsers.users -- loggedUser.map(_.id) + TestSettingsResponse.ProvideLocalUsers.SuccessResponse( + users = filteredLocalUsers.map(_.value.value).toList, + unknownUsers = dependencies.localUsers.unknownUsers + ) + case _: TestSettings.Invalidated => + TestSettingsResponse.ProvideLocalUsers.TestSettingsInvalidated("ROR Test settings are invalidated") + } + } + + private def forceReloadTestSettings(settings: RawRorSettings, + ttl: PositiveFiniteDuration) + (implicit requestId: RequestId): EitherT[Task, TestSettingsResponse, TestSettingsResponse] = { + EitherT( + rorInstance + .forceReloadTestSettingsEngine(settings, ttl) + .map { + _ + .map { newTestSettings => + TestSettingsResponse.UpdateTestSettings.SuccessResponse( + message = "updated settings", + validTo = newTestSettings.validTo, + warnings = newTestSettings.dependencies.impersonationWarningsReader.read().map(toWarningDto) + ) + } + .leftMap { + case IndexSettingsSavingError(error) => + TestSettingsResponse.UpdateTestSettings.FailedResponse(s"Cannot reload new settings: ${error.show}") + case ReloadError(RawSettingsReloadError.SettingsUpToDate(_)) => + TestSettingsResponse.UpdateTestSettings.FailedResponse(s"Current settings are already loaded") + case ReloadError(RawSettingsReloadError.RorInstanceStopped) => + TestSettingsResponse.UpdateTestSettings.FailedResponse(s"ROR instance is being stopped") + case ReloadError(RawSettingsReloadError.ReloadingFailed(failure)) => + TestSettingsResponse.UpdateTestSettings.FailedResponse(s"Cannot reload new settings: ${failure.message}") + } + } + ) + } + + private def toWarningDto(warning: ImpersonationWarning): TestSettingsResponse.Warning = { + TestSettingsResponse.Warning( + blockName = warning.block.value, + ruleName = warning.ruleName.value, + message = warning.message.value, + hint = warning.hint + ) + } +} + +object TestSettingsApi { + + final class Creator(settingsYamlParser: RawRorSettingsYamlParser) { + + def create(rorInstance: RorInstance): TestSettingsApi = { + new TestSettingsApi(rorInstance, settingsYamlParser) + } + } + + final case class TestSettingsRequest(aType: TestSettingsRequest.Type, + body: String) + + object TestSettingsRequest { + sealed trait Type + object Type { + case object ProvideTestSettings extends Type + case object UpdateTestSettings extends Type + case object InvalidateTestSettings extends Type + case object ProvideLocalUsers extends Type + } + } + + sealed trait TestSettingsResponse + object TestSettingsResponse { + + final case class Warning(blockName: String, + ruleName: String, + message: String, + hint: String) + + sealed trait ProvideTestSettings extends TestSettingsResponse + object ProvideTestSettings { + final case class CurrentTestSettings(ttl: FiniteDuration, + validTo: Instant, + settings: RawRorSettings, + warnings: List[Warning]) extends ProvideTestSettings + + final case class TestSettingsNotConfigured(message: String) extends ProvideTestSettings + final case class TestSettingsInvalidated(message: String, + settings: RawRorSettings, + ttl: FiniteDuration) extends ProvideTestSettings + } + + sealed trait UpdateTestSettings extends TestSettingsResponse + object UpdateTestSettings { + final case class SuccessResponse(message: String, validTo: Instant, warnings: List[Warning]) extends UpdateTestSettings + final case class FailedResponse(message: String) extends UpdateTestSettings + } + + sealed trait InvalidateTestSettings extends TestSettingsResponse + object InvalidateTestSettings { + final case class SuccessResponse(message: String) extends InvalidateTestSettings + final case class FailedResponse(message: String) extends InvalidateTestSettings + } + + sealed trait ProvideLocalUsers extends TestSettingsResponse + object ProvideLocalUsers { + final case class SuccessResponse(users: List[String], unknownUsers: Boolean) extends ProvideLocalUsers + final case class TestSettingsNotConfigured(message: String) extends ProvideLocalUsers + final case class TestSettingsInvalidated(message: String) extends ProvideLocalUsers + } + + sealed trait Failure extends TestSettingsResponse + object Failure { + final case class BadRequest(message: String) extends Failure + } + } + + implicit class StatusFromTestSettingsResponse(val response: TestSettingsResponse) extends AnyVal { + def status: String = response match { + case _: ProvideTestSettings.CurrentTestSettings => "TEST_SETTINGS_PRESENT" + case _: ProvideTestSettings.TestSettingsNotConfigured => "TEST_SETTINGS_NOT_CONFIGURED" + case _: ProvideTestSettings.TestSettingsInvalidated => "TEST_SETTINGS_INVALIDATED" + case _: UpdateTestSettings.SuccessResponse => "OK" + case _: UpdateTestSettings.FailedResponse => "FAILED" + case _: InvalidateTestSettings.SuccessResponse => "OK" + case _: InvalidateTestSettings.FailedResponse => "FAILED" + case _: ProvideLocalUsers.SuccessResponse => "OK" + case _: ProvideLocalUsers.TestSettingsNotConfigured => "TEST_SETTINGS_NOT_CONFIGURED" + case _: ProvideLocalUsers.TestSettingsInvalidated => "TEST_SETTINGS_INVALIDATED" + case _: Failure.BadRequest => "FAILED" + } + } + + private object Utils { + final case class UpdateTestSettingsRequest(settingsString: String, + ttl: PositiveFiniteDuration) + + private def parseDuration(value: String): Either[String, PositiveFiniteDuration] = { + Try(Duration(value)) + .toEither + .leftMap(_ => s"Cannot parse '${value.show}' as duration.") + .flatMap(_.toRefinedPositive) + } + + object decoders { + implicit val durationDecoder: Decoder[PositiveFiniteDuration] = Decoder.decodeString.emap(parseDuration) + + implicit val updateTestSettingsRequestDecoder: Decoder[UpdateTestSettingsRequest] = + Decoder.forProduct2("settings", "ttl")(UpdateTestSettingsRequest.apply) + } + + def apiFormat(duration: PositiveFiniteDuration): FiniteDuration = { + duration.value.toCoarsest + } + } +} diff --git a/core/src/main/scala/tech/beshu/ror/boot/ReadonlyRest.scala b/core/src/main/scala/tech/beshu/ror/boot/ReadonlyRest.scala index 4032538787..3640c428c1 100644 --- a/core/src/main/scala/tech/beshu/ror/boot/ReadonlyRest.scala +++ b/core/src/main/scala/tech/beshu/ror/boot/ReadonlyRest.scala @@ -20,207 +20,118 @@ import cats.data.{EitherT, NonEmptyList} import monix.eval.Task import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.audit.AuditingTool.AuditSettings import tech.beshu.ror.accesscontrol.audit.sink.AuditSinkServiceCreator -import tech.beshu.ror.accesscontrol.audit.{AuditEnvironmentContextBasedOnEsNodeSettings, AuditingTool, LoggingContext} +import tech.beshu.ror.accesscontrol.audit.{AuditingTool, LoggingContext} import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider import tech.beshu.ror.accesscontrol.blocks.mocks.{AuthServicesMocks, MutableMocksProviderWithCachePerRequest} -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex +import tech.beshu.ror.accesscontrol.domain.RorSettingsIndex import tech.beshu.ror.accesscontrol.factory.GlobalSettings.FlsEngine -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.{AsyncHttpClientsFactory, Core, CoreFactory, RawRorConfigBasedCoreFactory} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.{AsyncHttpClientsFactory, Core, CoreFactory, RawRorSettingsBasedCoreFactory} import tech.beshu.ror.accesscontrol.logging.AccessControlListLoggingDecorator -import tech.beshu.ror.audit.AuditEnvironmentContext import tech.beshu.ror.boot.ReadonlyRest.* -import tech.beshu.ror.configuration.* -import tech.beshu.ror.configuration.ConfigLoading.{ErrorOr, LoadRorConfig} -import tech.beshu.ror.configuration.TestConfigLoading.* -import tech.beshu.ror.configuration.index.{IndexConfigManager, IndexTestConfigManager} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.{EsEnv, IndexJsonContentService} +import tech.beshu.ror.es.{EsEnv, IndexDocumentManager} import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.* +import tech.beshu.ror.settings.ror.{MainRorSettings, RawRorSettings, TestRorSettings} import tech.beshu.ror.utils.DurationOps.PositiveFiniteDuration import java.time.Instant class ReadonlyRest(coreFactory: CoreFactory, - auditSinkServiceCreator: AuditSinkServiceCreator, - val indexConfigManager: IndexConfigManager, - val indexTestConfigManager: IndexTestConfigManager, - val authServicesMocksProvider: MutableMocksProviderWithCachePerRequest, - val esEnv: EsEnv) - (implicit environmentConfig: EnvironmentConfig, - scheduler: Scheduler) extends Logging { + indexDocumentManager: IndexDocumentManager, + auditSinkServiceCreator: AuditSinkServiceCreator) + (implicit systemContext: SystemContext, + scheduler: Scheduler) + extends Logging { - def start(): Task[Either[StartingFailure, RorInstance]] = { + private[boot] val authServicesMocksProvider = new MutableMocksProviderWithCachePerRequest(AuthServicesMocks.empty) + + def start(esConfigBasedRorSettings: EsConfigBasedRorSettings): Task[Either[StartingFailure, RorInstance]] = { (for { - esConfig <- loadEsConfig() - loadedRorConfig <- loadRorConfig(esConfig) - loadedTestRorConfig <- loadRorTestConfig(esConfig) - instance <- startRor(esConfig, loadedRorConfig, loadedTestRorConfig) + creatorsAndLoaders <- lift(SettingsRelatedCreatorsAndLoaders.create(esConfigBasedRorSettings, indexDocumentManager)) + loadedSettings <- EitherT(creatorsAndLoaders.startingRorSettingsLoader.load()).leftMap(StartingFailure(_)) + (loadedMainRorSettings, loadedTestRorSettings) = loadedSettings + instance <- startRor(esConfigBasedRorSettings, creatorsAndLoaders.creators, loadedMainRorSettings, loadedTestRorSettings) } yield instance).value } - private def loadEsConfig() = { - val action = ConfigLoading.loadEsConfig(esEnv) - runStartingFailureProgram(action) - } - - private def loadRorConfig(esConfig: EsConfig) = { - val action = - if (esConfig.rorEsLevelSettings.forceLoadRorFromFile) { - LoadRawRorConfig.loadFromFile(esEnv.configPath) - } else { - val loadingDelay = RorProperties.atStartupRorIndexSettingLoadingDelay(environmentConfig.propertiesProvider) - val loadingAttemptsCount = RorProperties.atStartupRorIndexSettingsLoadingAttemptsCount(environmentConfig.propertiesProvider) - val loadingAttemptsInterval = RorProperties.atStartupRorIndexSettingsLoadingAttemptsInterval(environmentConfig.propertiesProvider) - LoadRawRorConfig - .loadFromIndexWithFileFallback( - configurationIndex = esConfig.rorIndex.index, - loadingDelay = loadingDelay, - loadingAttemptsCount = loadingAttemptsCount, - loadingAttemptsInterval = loadingAttemptsInterval, - fallbackConfigFilePath = esEnv.configPath - ) - } - runStartingFailureProgram(action) - } - - private def loadRorTestConfig(esConfig: EsConfig): EitherT[Task, StartingFailure, LoadedTestRorConfig[TestRorConfig]] = { - val loadingDelay = RorProperties.atStartupRorIndexSettingLoadingDelay(environmentConfig.propertiesProvider) - val loadingAttemptsCount = RorProperties.atStartupRorIndexSettingsLoadingAttemptsCount(environmentConfig.propertiesProvider) - val loadingAttemptsInterval = RorProperties.atStartupRorIndexSettingsLoadingAttemptsInterval(environmentConfig.propertiesProvider) - val action = LoadRawTestRorConfig - .loadFromIndexWithFallback( - configurationIndex = esConfig.rorIndex.index, - loadingDelay = loadingDelay, - indexLoadingAttemptsCount = loadingAttemptsCount, - indexLoadingAttemptsInterval = loadingAttemptsInterval, - fallbackConfig = notSetTestRorConfig - ) - EitherT.right(runTestProgram(action)) - } - - private def runStartingFailureProgram[A](action: LoadRorConfig[ErrorOr[A]]) = { - val compiler = ConfigLoadingInterpreter.create(indexConfigManager) - EitherT(action.foldMap(compiler)) - .leftMap(toStartingFailure) - } - - private def toStartingFailure(error: LoadedRorConfig.Error) = { - error match { - case LoadedRorConfig.FileParsingError(message) => - StartingFailure(message) - case LoadedRorConfig.FileNotExist(path) => - StartingFailure(s"Cannot find settings file: ${path.show}") - case LoadedRorConfig.EsFileNotExist(path) => - StartingFailure(s"Cannot find elasticsearch settings file: [${path.show}]") - case LoadedRorConfig.EsFileMalformed(path, message) => - StartingFailure(s"Settings file is malformed: [${path.show}], ${message.show}") - case LoadedRorConfig.CannotUseRorConfigurationWhenXpackSecurityIsEnabled(typeOfConfiguration) => - StartingFailure(s"Cannot use ROR ${typeOfConfiguration.show} when XPack Security is enabled") - case LoadedRorConfig.IndexParsingError(message) => - StartingFailure(message) - case LoadedRorConfig.IndexUnknownStructure => - StartingFailure(s"Settings index is malformed") - case LoadedRorConfig.IndexNotExist => - StartingFailure(s"Settings index doesn't exist") - } - } - - private def runTestProgram(action: LoadTestRorConfig[IndexErrorOr[LoadedTestRorConfig[TestRorConfig]]]): Task[LoadedTestRorConfig[TestRorConfig]] = { - val compiler = TestConfigLoadingInterpreter.create(indexTestConfigManager) - EitherT(action.foldMap(compiler)) - .leftMap { - case LoadedTestRorConfig.IndexParsingError(message) => - logger.error(s"Loading ReadonlyREST test settings from index failed: ${message.show}. No test settings will be loaded.") - LoadedTestRorConfig.FallbackConfig(notSetTestRorConfig) - case LoadedTestRorConfig.IndexUnknownStructure => - logger.error("Loading ReadonlyREST test settings from index failed: index content malformed. No test settings will be loaded.") - LoadedTestRorConfig.FallbackConfig(notSetTestRorConfig) - case LoadedTestRorConfig.IndexNotExist => - logger.info("Loading ReadonlyREST test settings from index failed: cannot find index. No test settings will be loaded.") - LoadedTestRorConfig.FallbackConfig(notSetTestRorConfig) - } - .merge - } - - private def startRor(esConfig: EsConfig, - loadedConfig: LoadedRorConfig[RawRorConfig], - loadedTestRorConfig: LoadedTestRorConfig[TestRorConfig]) = { + private def startRor(esConfigBasedRorSettings: EsConfigBasedRorSettings, + creators: SettingsRelatedCreators, + loadedMainRorSettings: MainRorSettings, + loadedTestRorSettings: Option[TestRorSettings]) = { for { - mainEngine <- EitherT(loadRorCore(loadedConfig.value, esConfig.rorIndex.index)) - testEngine <- EitherT.right(loadTestEngine(esConfig, loadedTestRorConfig)) - rorInstance <- createRorInstance(esConfig.rorIndex.index, mainEngine, testEngine, loadedConfig) + mainEngine <- EitherT(loadRorEngine(loadedMainRorSettings.rawSettings, esConfigBasedRorSettings.settingsSource.settingsIndex)) + testEngine <- EitherT.right(loadTestEngine(loadedTestRorSettings, esConfigBasedRorSettings.settingsSource.settingsIndex)) + rorInstance <- createRorInstance(esConfigBasedRorSettings, creators, mainEngine, testEngine, loadedMainRorSettings) } yield rorInstance } - private def loadTestEngine(esConfig: EsConfig, loadedTestRorConfig: LoadedTestRorConfig[TestRorConfig]) = { - loadedTestRorConfig.value match { - case TestRorConfig.NotSet => + private def loadTestEngine(loadedTestRorSettings: Option[TestRorSettings], + settingsIndex: RorSettingsIndex) = { + loadedTestRorSettings match { + case None => Task.now(TestEngine.NotConfigured) - case config: TestRorConfig.Present if !config.isExpired(environmentConfig.clock) => - loadActiveTestEngine(esConfig, config) - case config: TestRorConfig.Present => - loadInvalidatedTestEngine(config) + case Some(settings) if !settings.isExpired(systemContext.clock) => + loadActiveTestEngine(settingsIndex, settings) + case Some(settings) => + loadInvalidatedTestEngine(settings) } } - private def loadActiveTestEngine(esConfig: EsConfig, testConfig: TestRorConfig.Present) = { + private def loadActiveTestEngine(settingsIndex: RorSettingsIndex, testSettings: TestRorSettings) = { for { - _ <- Task.delay(authServicesMocksProvider.update(testConfig.mocks)) - testEngine <- loadRorCore(testConfig.rawConfig, esConfig.rorIndex.index) + _ <- Task.delay(authServicesMocksProvider.update(testSettings.mocks)) + testEngine <- loadRorEngine(testSettings.rawSettings, settingsIndex) .map { case Right(loadedEngine) => TestEngine.Configured( engine = loadedEngine, - config = testConfig.rawConfig, - expiration = expirationConfig(testConfig.expiration) + settings = testSettings.rawSettings, + expiration = TestEngine.Expiration(testSettings.expiration.ttl, testSettings.expiration.validTo) ) case Left(startingFailure) => logger.error(s"Unable to start test engine. Cause: ${startingFailure.message.show}. Test settings engine will be marked as invalidated.") - TestEngine.Invalidated(testConfig.rawConfig, expirationConfig(testConfig.expiration)) + invalidatedTestEngine(testSettings) } } yield testEngine } - private def loadInvalidatedTestEngine(testConfig: TestRorConfig.Present) = { + private def loadInvalidatedTestEngine(testSettings: TestRorSettings) = { Task - .delay(authServicesMocksProvider.update(testConfig.mocks)) - .map { _ => - TestEngine.Invalidated(testConfig.rawConfig, expirationConfig(testConfig.expiration)) - } + .delay(authServicesMocksProvider.update(testSettings.mocks)) + .map { case () => invalidatedTestEngine(testSettings) } } - private def expirationConfig(config: TestRorConfig.Present.ExpirationConfig): TestEngine.Expiration = { - TestEngine.Expiration(config.ttl, config.validTo) + private def invalidatedTestEngine(testSettings: TestRorSettings) = { + TestEngine.Invalidated( + testSettings.rawSettings, + TestEngine.Expiration(testSettings.expiration.ttl, testSettings.expiration.validTo) + ) } - private def createRorInstance(rorConfigurationIndex: RorConfigurationIndex, - engine: Engine, + private def createRorInstance(esConfigBasedRorSettings: EsConfigBasedRorSettings, + creators: SettingsRelatedCreators, + mainEngine: Engine, testEngine: TestEngine, - loadedConfig: LoadedRorConfig[RawRorConfig]) = { + alreadyLoadedSettings: MainRorSettings) = { EitherT.right[StartingFailure] { - loadedConfig match { - case LoadedRorConfig.FileConfig(config) => - RorInstance.createWithPeriodicIndexCheck(this, MainEngine(engine, config), testEngine, rorConfigurationIndex) - case LoadedRorConfig.ForcedFileConfig(config) => - RorInstance.createWithoutPeriodicIndexCheck(this, MainEngine(engine, config), testEngine, rorConfigurationIndex) - case LoadedRorConfig.IndexConfig(_, config) => - RorInstance.createWithPeriodicIndexCheck(this, MainEngine(engine, config), testEngine, rorConfigurationIndex) - } + RorInstance.create(this, esConfigBasedRorSettings, creators, MainEngine(mainEngine, alreadyLoadedSettings.rawSettings), testEngine) } } - private[ror] def loadRorCore(config: RawRorConfig, - rorIndexNameConfiguration: RorConfigurationIndex): Task[Either[StartingFailure, Engine]] = { + private[ror] def loadRorEngine(settings: RawRorSettings, + settingsIndex: RorSettingsIndex): Task[Either[StartingFailure, Engine]] = { val httpClientsFactory = new AsyncHttpClientsFactory val ldapConnectionPoolProvider = new UnboundidLdapConnectionPoolProvider EitherT( coreFactory - .createCoreFrom(config, rorIndexNameConfiguration, httpClientsFactory, ldapConnectionPoolProvider, authServicesMocksProvider) + .createCoreFrom(settings, settingsIndex, httpClientsFactory, ldapConnectionPoolProvider, authServicesMocksProvider) ) .flatMap(core => createEngine(httpClientsFactory, ldapConnectionPoolProvider, core)) .semiflatTap { engine => @@ -234,15 +145,15 @@ class ReadonlyRest(coreFactory: CoreFactory, ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider, core: Core): EitherT[Task, NonEmptyList[CoreCreationError], Engine] = { implicit val loggingContext: LoggingContext = LoggingContext(core.accessControl.staticContext.obfuscatedHeaders) - implicit val auditEnvironmentContext: AuditEnvironmentContext = new AuditEnvironmentContextBasedOnEsNodeSettings(esEnv.esNodeSettings) - EitherT(createAuditingTool(core)) + EitherT(createAuditingTool(core.auditingSettings)) .map { auditingTool => val decoratedCore = Core( accessControl = new AccessControlListLoggingDecorator( underlying = core.accessControl, auditingTool = auditingTool ), - rorConfig = core.rorConfig + dependencies = core.dependencies, + auditingSettings = core.auditingSettings ) new Engine( core = decoratedCore, @@ -253,10 +164,12 @@ class ReadonlyRest(coreFactory: CoreFactory, } } - private def createAuditingTool(core: Core) + private def createAuditingTool(auditingSettings: Option[AuditSettings]) (implicit loggingContext: LoggingContext): Task[Either[NonEmptyList[CoreCreationError], Option[AuditingTool]]] = { - core.rorConfig.auditingSettings - .map(settings => AuditingTool.create(settings, auditSinkServiceCreator)(using environmentConfig.clock, loggingContext)) + auditingSettings + .map { settings => + AuditingTool.create(settings, auditSinkServiceCreator)(using systemContext.clock, loggingContext) + } .sequence .map { _.sequence @@ -276,7 +189,7 @@ class ReadonlyRest(coreFactory: CoreFactory, } } - private def handleLoadingCoreErrors(errors: NonEmptyList[RawRorConfigBasedCoreFactory.CoreCreationError]) = { + private def handleLoadingCoreErrors(errors: NonEmptyList[RawRorSettingsBasedCoreFactory.CoreCreationError]) = { val errorsMessage = errors .map(_.reason) .map { @@ -288,15 +201,15 @@ class ReadonlyRest(coreFactory: CoreFactory, StartingFailure(errorsMessage) } - private def notSetTestRorConfig: TestRorConfig = TestRorConfig.NotSet + private def lift[A](value: => A) = { + EitherT.liftF(Task.delay(value)) + } } object ReadonlyRest { - final case class StartingFailure(message: String, throwable: Option[Throwable] = None) - final case class MainEngine(engine: Engine, - config: RawRorConfig) + settings: RawRorSettings) sealed trait TestEngine @@ -304,10 +217,10 @@ object ReadonlyRest { object NotConfigured extends TestEngine final case class Configured(engine: Engine, - config: RawRorConfig, + settings: RawRorSettings, expiration: Expiration) extends TestEngine - final case class Invalidated(config: RawRorConfig, + final case class Invalidated(settings: RawRorSettings, expiration: Expiration) extends TestEngine final case class Expiration(ttl: PositiveFiniteDuration, validTo: Instant) @@ -326,25 +239,22 @@ object ReadonlyRest { } } - def create(indexContentService: IndexJsonContentService, + final case class StartingFailure(message: String, throwable: Option[Throwable] = None) + + def create(indexContentService: IndexDocumentManager, auditSinkServiceCreator: AuditSinkServiceCreator, env: EsEnv) (implicit scheduler: Scheduler, - environmentConfig: EnvironmentConfig): ReadonlyRest = { - val coreFactory: CoreFactory = new RawRorConfigBasedCoreFactory(env.esVersion) - create(coreFactory, indexContentService, auditSinkServiceCreator, env) + systemContext: SystemContext): ReadonlyRest = { + val coreFactory: CoreFactory = new RawRorSettingsBasedCoreFactory(env) + create(coreFactory, indexContentService, auditSinkServiceCreator) } def create(coreFactory: CoreFactory, - indexContentService: IndexJsonContentService, - auditSinkServiceCreator: AuditSinkServiceCreator, - env: EsEnv) + indexDocumentManager: IndexDocumentManager, + auditSinkServiceCreator: AuditSinkServiceCreator) (implicit scheduler: Scheduler, - environmentConfig: EnvironmentConfig): ReadonlyRest = { - val indexConfigManager: IndexConfigManager = new IndexConfigManager(indexContentService) - val indexTestConfigManager: IndexTestConfigManager = new IndexTestConfigManager(indexContentService) - val mocksProvider = new MutableMocksProviderWithCachePerRequest(AuthServicesMocks.empty) - - new ReadonlyRest(coreFactory, auditSinkServiceCreator, indexConfigManager, indexTestConfigManager, mocksProvider, env) + systemContext: SystemContext): ReadonlyRest = { + new ReadonlyRest(coreFactory, indexDocumentManager, auditSinkServiceCreator) } } \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/boot/RorInstance.scala b/core/src/main/scala/tech/beshu/ror/boot/RorInstance.scala index 8a0ac8e9ec..e3d95bc477 100644 --- a/core/src/main/scala/tech/beshu/ror/boot/RorInstance.scala +++ b/core/src/main/scala/tech/beshu/ror/boot/RorInstance.scala @@ -16,194 +16,141 @@ */ package tech.beshu.ror.boot -import cats.Show import cats.effect.Resource -import cats.implicits.toShow import cats.syntax.either.* import monix.catnap.Semaphore import monix.eval.Task -import monix.execution.{Cancelable, Scheduler} +import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.blocks.mocks.{AuthServicesMocks, MocksProvider} -import tech.beshu.ror.accesscontrol.domain.{RequestId, RorConfigurationIndex} -import tech.beshu.ror.api.{AuthMockApi, ConfigApi, TestConfigApi} -import tech.beshu.ror.boot.engines.{Engines, MainConfigBasedReloadableEngine, TestConfigBasedReloadableEngine} -import tech.beshu.ror.configuration.RorProperties.RefreshInterval -import tech.beshu.ror.configuration.index.{IndexConfigError, SavingIndexConfigError} -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError -import tech.beshu.ror.configuration.loader.FileConfigLoader -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig, RorConfig, RorProperties} -import tech.beshu.ror.implicits.* +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.accesscontrol.factory.RorDependencies +import tech.beshu.ror.api.{AuthMockApi, MainSettingsApi, TestSettingsApi} +import tech.beshu.ror.boot.ReadonlyRest.StartingFailure +import tech.beshu.ror.boot.engines.Engines +import tech.beshu.ror.settings.es.RorCoreSettingsLoadingStrategy.CoreRefreshSettings +import tech.beshu.ror.settings.es.{EsConfigBasedRorSettings, RorCoreSettingsLoadingStrategy} +import tech.beshu.ror.settings.ror.source.IndexSettingsSource +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource.SettingsLoadingError +import tech.beshu.ror.settings.ror.source.ReadWriteSettingsSource.SettingsSavingError +import tech.beshu.ror.settings.ror.{MainRorSettings, RawRorSettings} import tech.beshu.ror.utils.DurationOps.PositiveFiniteDuration import java.time.Instant +import java.util.UUID class RorInstance private(boot: ReadonlyRest, mode: RorInstance.Mode, - initialEngine: ReadonlyRest.MainEngine, + esConfigBasedRorSettings: EsConfigBasedRorSettings, + creators: SettingsRelatedCreators, + mainInitialEngine: ReadonlyRest.MainEngine, mainReloadInProgress: Semaphore[Task], - initialTestEngine: ReadonlyRest.TestEngine, - testReloadInProgress: Semaphore[Task], - rorConfigurationIndex: RorConfigurationIndex) - (implicit environmentConfig: EnvironmentConfig, + testInitialEngine: ReadonlyRest.TestEngine, + testReloadInProgress: Semaphore[Task]) + (implicit systemContext: SystemContext, scheduler: Scheduler) extends Logging { import RorInstance.* - import RorInstance.ScheduledReloadError.{EngineReloadError, ReloadingInProgress} - - logger.info("ReadonlyREST was loaded ...") - private val configsReloadTask = mode match { - case Mode.WithPeriodicIndexCheck => - RorProperties.rorIndexSettingsReloadInterval(environmentConfig.propertiesProvider) match { - case RefreshInterval.Disabled => - logger.info(s"[CLUSTERWIDE SETTINGS] Scheduling in-index settings check disabled") - Cancelable.empty - case RefreshInterval.Enabled(interval) => - scheduleEnginesReload(interval) - } - case Mode.NoPeriodicIndexCheck => - logger.info(s"[CLUSTERWIDE SETTINGS] Scheduling in-index settings check disabled") - Cancelable.empty + import creators.* + + private val settingsAutoReloader = mode match { + case Mode.WithPeriodicIndexCheck(interval) => new EnabledRorSettingsAutoReloader(interval, this) + case Mode.NoPeriodicIndexCheck => DisabledRorSettingsAutoReloader } - private val aMainConfigEngine = new MainConfigBasedReloadableEngine( + private val theMainSettingsEngine = mainSettingsBasedReloadableEngineCreator.create( boot, - (initialEngine.engine, initialEngine.config), - mainReloadInProgress, - rorConfigurationIndex, + esConfigBasedRorSettings, + mainInitialEngine, + mainReloadInProgress ) - private val anTestConfigEngine = TestConfigBasedReloadableEngine.create( + private val theTestSettingsEngine = testSettingsBasedReloadableEngineCreator.create( boot, - initialTestEngine, - testReloadInProgress, - rorConfigurationIndex + esConfigBasedRorSettings, + testInitialEngine, + testReloadInProgress ) - private val configRestApi = new ConfigApi( - rorInstance = this, - boot.indexConfigManager, - new FileConfigLoader(boot.esEnv.configPath), - rorConfigurationIndex - ) + private val mainSettingsRestApi = mainSettingsApiCreator.create(this) + private val testSettingsRestApi = testSettingsApiCreator.create(this) + private val authMockRestApi = new AuthMockApi(rorInstance = this) - private val authMockRestApi = new AuthMockApi( - rorInstance = this - ) + settingsAutoReloader.start() - private val testConfigRestApi = new TestConfigApi(this) + val id: String = UUID.randomUUID().toString + logger.info(s"[$id] ReadonlyREST was loaded!") - def engines: Option[Engines] = aMainConfigEngine.engine.map(Engines(_, anTestConfigEngine.engine)) + def engines: Option[Engines] = theMainSettingsEngine.engine.map(Engines(_, theTestSettingsEngine.engine)) - def configApi: ConfigApi = configRestApi + def mainSettingsApi: MainSettingsApi = mainSettingsRestApi def authMockApi: AuthMockApi = authMockRestApi - def testConfigApi: TestConfigApi = testConfigRestApi + def testSettingsApi: TestSettingsApi = testSettingsRestApi def mocksProvider: MocksProvider = boot.authServicesMocksProvider def forceReloadFromIndex() - (implicit requestId: RequestId): Task[Either[IndexConfigReloadError, Unit]] = - aMainConfigEngine.forceReloadFromIndex() + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadError, Unit]] = + theMainSettingsEngine.forceReloadFromIndex() - def forceReloadAndSave(config: RawRorConfig) - (implicit requestId: RequestId): Task[Either[IndexConfigReloadWithUpdateError, Unit]] = - aMainConfigEngine.forceReloadAndSave(config) + def forceReloadAndSave(settings: RawRorSettings) + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadWithUpdateError, Unit]] = + theMainSettingsEngine.forceReloadAndSave(MainRorSettings(settings)) - def currentTestConfig() - (implicit requestId: RequestId): Task[TestConfig] = { - anTestConfigEngine.currentTestConfig() + def currentTestSettings() + (implicit requestId: RequestId): Task[TestSettings] = { + theTestSettingsEngine.currentTestSettings() } - def forceReloadTestConfigEngine(config: RawRorConfig, - ttl: PositiveFiniteDuration) - (implicit requestId: RequestId): Task[Either[IndexConfigReloadWithUpdateError, TestConfig.Present]] = { - anTestConfigEngine.forceReloadTestConfigEngine(config, ttl) + def forceReloadTestSettingsEngine(settings: RawRorSettings, + ttl: PositiveFiniteDuration) + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadWithUpdateError, TestSettings.Present]] = { + theTestSettingsEngine.forceReloadTestSettingsEngine(settings, ttl) } - def invalidateTestConfigEngine() - (implicit requestId: RequestId): Task[Either[IndexConfigInvalidationError, Unit]] = { - anTestConfigEngine.invalidateTestConfigEngine() + def invalidateTestSettingsEngine() + (implicit requestId: RequestId): Task[Either[IndexSettingsInvalidationError, Unit]] = { + theTestSettingsEngine.invalidateTestSettingsEngine() } def updateAuthMocks(mocks: AuthServicesMocks) - (implicit requestId: RequestId): Task[Either[IndexConfigUpdateError, Unit]] = { - anTestConfigEngine.saveConfig(mocks) + (implicit requestId: RequestId): Task[Either[IndexSettingsUpdateError, Unit]] = { + theTestSettingsEngine.saveServicesMocks(mocks) } def stop(): Task[Unit] = { implicit val requestId: RequestId = RequestId("ES sigterm") for { - _ <- Task.delay(configsReloadTask.cancel()) - _ <- anTestConfigEngine.stop() - _ <- aMainConfigEngine.stop() + _ <- Task.delay(logger.info("ReadonlyREST is stopping ...")) + _ <- settingsAutoReloader.stop() + _ <- theTestSettingsEngine.stop() + _ <- theMainSettingsEngine.stop() + _ <- Task.delay(logger.info("ReadonlyREST is stopped!")) } yield () } - private def scheduleEnginesReload(interval: PositiveFiniteDuration): Cancelable = { - val reloadTask = { (requestId: RequestId) => - Task.sequence { - Seq( - tryMainEngineReload(requestId).map(result => (ConfigType.Main, result)), - tryTestEngineReload(requestId).map(result => (ConfigType.Test, result)) - ) - } - } - scheduleIndexConfigChecking(interval, reloadTask) - } - - private def scheduleIndexConfigChecking(interval: PositiveFiniteDuration, - reloadTask: RequestId => Task[Seq[(ConfigType, Either[ScheduledReloadError, Unit])]]): Cancelable = { - logger.debug(s"[CLUSTERWIDE SETTINGS] Scheduling next in-index settings check within ${interval.show}") - scheduler.scheduleOnce(interval.value) { - implicit val requestId: RequestId = RequestId(environmentConfig.uuidProvider.random.toString) - logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Loading ReadonlyREST configs from index ...") - reloadTask(requestId) - .runAsync { - case Right(reloadResults) => - reloadResults.foreach(logConfigReloadResult) - scheduleIndexConfigChecking(interval, reloadTask) - case Left(ex) => - logger.error(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Checking index config failed: error", ex) - scheduleIndexConfigChecking(interval, reloadTask) - } - } - } - - private def logConfigReloadResult(configReloadResult: (ConfigType, Either[ScheduledReloadError, Unit])) - (implicit requestId: RequestId): Unit = configReloadResult match { - case (_, Right(())) => - case (name, Left(ReloadingInProgress)) => - logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Reloading of ${name.show} engine in progress ... skipping") - case (name, Left(EngineReloadError(IndexConfigReloadError.ReloadError(RawConfigReloadError.ConfigUpToDate(_))))) => - logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] ${name.show} settings are up to date. Nothing to reload.") - case (name, Left(EngineReloadError(IndexConfigReloadError.ReloadError(RawConfigReloadError.RorInstanceStopped)))) => - logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Stopping periodic ${name.show} settings check - application is being stopped") - case (name, Left(EngineReloadError(IndexConfigReloadError.ReloadError(RawConfigReloadError.ReloadingFailed(startingFailure))))) => - logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] ReadonlyREST ${name.show} engine starting failed: ${startingFailure.message.show}") - case (name, Left(EngineReloadError(IndexConfigReloadError.LoadingConfigError(error)))) => - logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Loading ${name.show} config from index failed: ${error.show}") - } - - private def tryMainEngineReload(requestId: RequestId): Task[Either[ScheduledReloadError, Unit]] = { + private [boot] def tryMainEngineReload(requestId: RequestId): Task[Either[ScheduledReloadError, Unit]] = { withGuard(mainReloadInProgress) { - aMainConfigEngine - .reloadEngineUsingIndexConfigWithoutPermit()(requestId) + theMainSettingsEngine + .reloadEngineUsingIndexSettingsWithoutPermit()(requestId) .map(_.map(_ => ())) .map(_.leftMap(ScheduledReloadError.EngineReloadError.apply)) } } - private def tryTestEngineReload(requestId: RequestId): Task[Either[ScheduledReloadError, Unit]] = { + private [boot] def tryTestEngineReload(requestId: RequestId): Task[Either[ScheduledReloadError, Unit]] = { withGuard(testReloadInProgress) { - anTestConfigEngine - .reloadEngineUsingIndexConfigWithoutPermit()(requestId) + theTestSettingsEngine + .reloadEngineUsingIndexSettingsWithoutPermit()(requestId) .map(_.leftMap(ScheduledReloadError.EngineReloadError.apply)) } } - private def withGuard(semaphore: Semaphore[Task])(action: => Task[Either[ScheduledReloadError, Unit]]) = { + private def withGuard(semaphore: Semaphore[Task]) + (action: => Task[Either[ScheduledReloadError, Unit]]) = { val criticalSection = Resource.make(semaphore.tryAcquire) { case true => semaphore.release @@ -219,108 +166,100 @@ class RorInstance private(boot: ReadonlyRest, object RorInstance { - sealed trait RawConfigReloadError - object RawConfigReloadError { - final case class ReloadingFailed(failure: ReadonlyRest.StartingFailure) extends RawConfigReloadError - final case class ConfigUpToDate(config: RawRorConfig) extends RawConfigReloadError - object RorInstanceStopped extends RawConfigReloadError - } - - sealed trait IndexConfigReloadWithUpdateError - object IndexConfigReloadWithUpdateError { - final case class ReloadError(undefined: RawConfigReloadError) extends IndexConfigReloadWithUpdateError - final case class IndexConfigSavingError(underlying: SavingIndexConfigError) extends IndexConfigReloadWithUpdateError + def create(boot: ReadonlyRest, + esConfigBasedRorSettings: EsConfigBasedRorSettings, + creators: SettingsRelatedCreators, + mainEngine: ReadonlyRest.MainEngine, + testEngine: ReadonlyRest.TestEngine) + (implicit systemContext: SystemContext, + scheduler: Scheduler): Task[RorInstance] = { + for { + isReloadInProgressSemaphore <- Semaphore[Task](1) + isTestReloadInProgressSemaphore <- Semaphore[Task](1) + } yield new RorInstance( + boot = boot, + esConfigBasedRorSettings = esConfigBasedRorSettings, + creators = creators, + mode = modeFrom(esConfigBasedRorSettings.rorCoreSettingsLoadingStrategy), + mainInitialEngine = mainEngine, + mainReloadInProgress = isReloadInProgressSemaphore, + testInitialEngine = testEngine, + testReloadInProgress = isTestReloadInProgressSemaphore + ) } - sealed trait IndexConfigReloadError - object IndexConfigReloadError { - final case class LoadingConfigError(underlying: ConfigLoaderError[IndexConfigError]) extends IndexConfigReloadError - final case class ReloadError(underlying: RawConfigReloadError) extends IndexConfigReloadError + private def modeFrom(strategy: RorCoreSettingsLoadingStrategy) = { + strategy match { + case RorCoreSettingsLoadingStrategy.ForceLoadingFromFileSettings => + Mode.NoPeriodicIndexCheck + case RorCoreSettingsLoadingStrategy.LoadFromIndexWithFileFallback(_, CoreRefreshSettings.Disabled) => + Mode.NoPeriodicIndexCheck + case RorCoreSettingsLoadingStrategy.LoadFromIndexWithFileFallback(_, CoreRefreshSettings.Enabled(refreshInterval)) => + Mode.WithPeriodicIndexCheck(refreshInterval) + } } - sealed trait IndexConfigUpdateError - object IndexConfigUpdateError { - final case class IndexConfigSavingError(underlying: SavingIndexConfigError) extends IndexConfigUpdateError - case object TestSettingsNotSet extends IndexConfigUpdateError - case object TestSettingsInvalidated extends IndexConfigUpdateError + sealed trait RawSettingsReloadError + object RawSettingsReloadError { + final case class ReloadingFailed(failure: StartingFailure) extends RawSettingsReloadError + final case class SettingsUpToDate(settings: RawRorSettings) extends RawSettingsReloadError + object RorInstanceStopped extends RawSettingsReloadError } - sealed trait IndexConfigInvalidationError - object IndexConfigInvalidationError { - final case class IndexConfigSavingError(underlying: SavingIndexConfigError) extends IndexConfigInvalidationError + sealed trait IndexSettingsReloadWithUpdateError + object IndexSettingsReloadWithUpdateError { + final case class ReloadError(undefined: RawSettingsReloadError) + extends IndexSettingsReloadWithUpdateError + final case class IndexSettingsSavingError(underlying: SettingsSavingError[IndexSettingsSource.SavingError]) + extends IndexSettingsReloadWithUpdateError } - private sealed trait ScheduledReloadError - private object ScheduledReloadError { - case object ReloadingInProgress extends ScheduledReloadError - final case class EngineReloadError(underlying: IndexConfigReloadError) extends ScheduledReloadError + sealed trait IndexSettingsReloadError + object IndexSettingsReloadError { + final case class IndexLoadingSettingsError(underlying: SettingsLoadingError[IndexSettingsSource.LoadingError]) + extends IndexSettingsReloadError + final case class ReloadError(underlying: RawSettingsReloadError) + extends IndexSettingsReloadError } - sealed trait TestConfig - object TestConfig { - case object NotSet extends TestConfig - final case class Present(config: RorConfig, - rawConfig: RawRorConfig, - configuredTtl: PositiveFiniteDuration, - validTo: Instant) extends TestConfig - final case class Invalidated(recent: RawRorConfig, - configuredTtl: PositiveFiniteDuration) extends TestConfig + sealed trait IndexSettingsUpdateError + object IndexSettingsUpdateError { + final case class IndexSettingsSavingError(underlying: SettingsSavingError[IndexSettingsSource.SavingError]) + extends IndexSettingsUpdateError + case object TestSettingsNotSet + extends IndexSettingsUpdateError + case object TestSettingsInvalidated + extends IndexSettingsUpdateError } - def createWithPeriodicIndexCheck(boot: ReadonlyRest, - mainEngine: ReadonlyRest.MainEngine, - testEngine: ReadonlyRest.TestEngine, - rorConfigurationIndex: RorConfigurationIndex) - (implicit environmentConfig: EnvironmentConfig, - scheduler: Scheduler): Task[RorInstance] = { - create(boot, Mode.WithPeriodicIndexCheck, mainEngine, testEngine, rorConfigurationIndex) + sealed trait IndexSettingsInvalidationError + object IndexSettingsInvalidationError { + final case class IndexSettingsSavingError(underlying: SettingsSavingError[IndexSettingsSource.SavingError]) + extends IndexSettingsInvalidationError } - def createWithoutPeriodicIndexCheck(boot: ReadonlyRest, - mainEngine: ReadonlyRest.MainEngine, - testEngine: ReadonlyRest.TestEngine, - rorConfigurationIndex: RorConfigurationIndex) - (implicit environmentConfig: EnvironmentConfig, - scheduler: Scheduler): Task[RorInstance] = { - create(boot, Mode.NoPeriodicIndexCheck, mainEngine, testEngine, rorConfigurationIndex) + private [boot] sealed trait ScheduledReloadError + private [boot] object ScheduledReloadError { + case object ReloadingInProgress extends ScheduledReloadError + final case class EngineReloadError(underlying: IndexSettingsReloadError) extends ScheduledReloadError } - private def create(boot: ReadonlyRest, - mode: RorInstance.Mode, - engine: ReadonlyRest.MainEngine, - testEngine: ReadonlyRest.TestEngine, - rorConfigurationIndex: RorConfigurationIndex) - (implicit environmentConfig: EnvironmentConfig, - scheduler: Scheduler) = { - for { - isReloadInProgressSemaphore <- Semaphore[Task](1) - isTestReloadInProgressSemaphore <- Semaphore[Task](1) - } yield new RorInstance( - boot = boot, - mode = mode, - initialEngine = engine, - mainReloadInProgress = isReloadInProgressSemaphore, - initialTestEngine = testEngine, - testReloadInProgress = isTestReloadInProgressSemaphore, - rorConfigurationIndex = rorConfigurationIndex - ) + sealed trait TestSettings + object TestSettings { + case object NotSet extends TestSettings + final case class Present(rawSettings: RawRorSettings, + dependencies: RorDependencies, + configuredTtl: PositiveFiniteDuration, + validTo: Instant) extends TestSettings + final case class Invalidated(recent: RawRorSettings, + configuredTtl: PositiveFiniteDuration) extends TestSettings } private sealed trait Mode private object Mode { - case object WithPeriodicIndexCheck extends Mode + final case class WithPeriodicIndexCheck(reloadInterval: PositiveFiniteDuration) extends Mode case object NoPeriodicIndexCheck extends Mode } - private sealed trait ConfigType - private object ConfigType { - case object Main extends ConfigType - case object Test extends ConfigType - - implicit val show: Show[ConfigType] = Show.show { - case Main => "main" - case Test => "test" - } - } } diff --git a/core/src/main/scala/tech/beshu/ror/boot/RorSettingsAutoReloader.scala b/core/src/main/scala/tech/beshu/ror/boot/RorSettingsAutoReloader.scala new file mode 100644 index 0000000000..cba9edf674 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/boot/RorSettingsAutoReloader.scala @@ -0,0 +1,163 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.boot + +import cats.Show +import cats.implicits.toShow +import monix.eval.Task +import monix.execution.{Cancelable, Scheduler} +import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.boot.RorInstance.ScheduledReloadError.{EngineReloadError, ReloadingInProgress} +import tech.beshu.ror.boot.RorInstance.{IndexSettingsReloadError, RawSettingsReloadError, ScheduledReloadError} +import tech.beshu.ror.utils.DurationOps.PositiveFiniteDuration +import tech.beshu.ror.implicits.* + +import java.util.concurrent.atomic.AtomicReference + +trait RorSettingsAutoReloader { + def start(): Unit + def stop(): Task[Unit] +} + +class EnabledRorSettingsAutoReloader(reloadInterval: PositiveFiniteDuration, + instance: RorInstance) + (implicit systemContext: SystemContext, + scheduler: Scheduler) + extends RorSettingsAutoReloader with Logging { + + private val reloadTaskState: AtomicReference[ReloadTaskState] = new AtomicReference(ReloadTaskState.NotInitiated) + + override def start(): Unit = { + logger.info(s"[CLUSTERWIDE SETTINGS] Auto reloading of ReadonlyREST in-index settings enabled") + scheduleEnginesReload(reloadInterval) + } + + override def stop(): Task[Unit] = { + for { + currentState <- Task.delay(reloadTaskState.getAndSet(ReloadTaskState.Stopped)) + _ <- Task.delay(currentState match { + case ReloadTaskState.NotInitiated => // do nothing + case ReloadTaskState.Running(cancelable) => cancelable.cancel() + case ReloadTaskState.Stopped => // do nothing + }) + } yield () + } + + private def scheduleEnginesReload(interval: PositiveFiniteDuration): Unit = { + val reloadTask = { (requestId: RequestId) => + Task.sequence { + Seq( + instance.tryMainEngineReload(requestId).map(result => (SettingsType.Main, result)), + instance.tryTestEngineReload(requestId).map(result => (SettingsType.Test, result)) + ) + } + } + scheduleNextIfNotStopping(interval, reloadTask) + } + + private def scheduleNextIfNotStopping(interval: PositiveFiniteDuration, + reloadTask: RequestId => Task[Seq[(SettingsType, Either[ScheduledReloadError, Unit])]]): Unit = { + implicit val requestId: RequestId = RequestId(systemContext.uuidProvider.random.toString) + val nextTask = scheduleIndexSettingsChecking(interval, reloadTask) + trySetNextReloadTask(nextTask) match { + case ReloadTaskState.NotInitiated => // nothing to do + case ReloadTaskState.Running(_) => // nothing to do + case ReloadTaskState.Stopped => nextTask.cancel() + } + } + + private def scheduleIndexSettingsChecking(interval: PositiveFiniteDuration, + reloadTask: RequestId => Task[Seq[(SettingsType, Either[ScheduledReloadError, Unit])]]) + (implicit requestId: RequestId): CancelableWithRequestId = { + logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Scheduling next in-index settings check within ${interval.show}") + val cancellable = scheduler.scheduleOnce(interval.value) { + logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Loading ReadonlyREST settings from index ...") + reloadTask(requestId) + .runAsync { + case Right(reloadResults) => + reloadResults.foreach(logSettingsReloadResult) + scheduleNextIfNotStopping(interval, reloadTask) + case Left(ex) => + logger.error(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Checking index settings failed: ${ex.getMessage}") + scheduleNextIfNotStopping(interval, reloadTask) + } + } + new CancelableWithRequestId(cancellable, requestId) + } + + private def trySetNextReloadTask(nextTask: CancelableWithRequestId) = { + reloadTaskState.updateAndGet { + case ReloadTaskState.NotInitiated | ReloadTaskState.Running(_) => + ReloadTaskState.Running(nextTask) + case ReloadTaskState.Stopped => + ReloadTaskState.Stopped + } + } + + private def logSettingsReloadResult(settingsReloadResult: (SettingsType, Either[ScheduledReloadError, Unit])) + (implicit requestId: RequestId): Unit = settingsReloadResult match { + case (_, Right(())) => + case (name, Left(ReloadingInProgress)) => + logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Reloading of ${name.show} engine in progress ... skipping") + case (name, Left(EngineReloadError(IndexSettingsReloadError.ReloadError(RawSettingsReloadError.SettingsUpToDate(_))))) => + logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] ${name.show} settings are up to date. Nothing to reload.") + case (name, Left(EngineReloadError(IndexSettingsReloadError.ReloadError(RawSettingsReloadError.RorInstanceStopped)))) => + logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Stopping periodic ${name.show} settings check - application is being stopped") + case (name, Left(EngineReloadError(IndexSettingsReloadError.ReloadError(RawSettingsReloadError.ReloadingFailed(startingFailure))))) => + logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] ReadonlyREST ${name.show} engine starting failed: ${startingFailure.message.show}") + case (name, Left(EngineReloadError(IndexSettingsReloadError.IndexLoadingSettingsError(error)))) => + logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Loading ${name.show} settings from index failed: ${error.show}") + } + + private sealed trait SettingsType + private object SettingsType { + case object Main extends SettingsType + case object Test extends SettingsType + + implicit val show: Show[SettingsType] = Show.show { + case Main => "main" + case Test => "test" + } + } + + private sealed trait ReloadTaskState + private object ReloadTaskState { + case object NotInitiated extends ReloadTaskState + final case class Running(cancelable: CancelableWithRequestId) extends ReloadTaskState + case object Stopped extends ReloadTaskState + } + + private final class CancelableWithRequestId(cancelable: Cancelable, requestId: RequestId) + extends Logging { + + def cancel(): Unit = { + logger.debug(s"[CLUSTERWIDE SETTINGS][${requestId.show}] Scheduling next in-index settings check cancelled!") + cancelable.cancel() + } + } +} + +object DisabledRorSettingsAutoReloader extends RorSettingsAutoReloader with Logging { + + override def start(): Unit = { + logger.info(s"[CLUSTERWIDE SETTINGS] Auto reloading of ReadonlyREST in-index settings disabled") + } + + override def stop(): Task[Unit] = Task.unit +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/boot/SecurityProviderConfiguratorForFips.scala b/core/src/main/scala/tech/beshu/ror/boot/SecurityProviderConfiguratorForFips.scala index 0c1fc7e5e2..97cc92bf0b 100644 --- a/core/src/main/scala/tech/beshu/ror/boot/SecurityProviderConfiguratorForFips.scala +++ b/core/src/main/scala/tech/beshu/ror/boot/SecurityProviderConfiguratorForFips.scala @@ -18,22 +18,30 @@ package tech.beshu.ror.boot import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider -import tech.beshu.ror.configuration.FipsConfiguration -import tech.beshu.ror.configuration.FipsConfiguration.FipsMode +import tech.beshu.ror.settings.es.RorSslSettings +import tech.beshu.ror.settings.es.SslSettings.FipsMode.{NonFips, SslOnly} import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import java.security.Security object SecurityProviderConfiguratorForFips { - def configureIfRequired(fipsConfiguration: FipsConfiguration): Unit = { - fipsConfiguration.fipsMode match { - case FipsMode.SslOnly => + def configureIfRequired(ssl: RorSslSettings): Unit = { + val fipsModes = ssl match { + case RorSslSettings.OnlyExternalSslSettings(ssl) => ssl.fipsMode :: Nil + case RorSslSettings.OnlyInternodeSslSettings(ssl) => ssl.fipsMode :: Nil + case RorSslSettings.ExternalAndInternodeSslSettings(external, internode) => external.fipsMode :: internode.fipsMode :: Nil + } + fipsModes + .find { + case SslOnly => true + case NonFips => false + } + .foreach { _ => doPrivileged { Security.insertProviderAt(new BouncyCastleFipsProvider(), 1) // basic encryption provider Security.insertProviderAt(new BouncyCastleJsseProvider("fips:BCFIPS"), 2) // tls } - case FipsMode.NonFips => - } + } } } diff --git a/core/src/main/scala/tech/beshu/ror/boot/SettingsRelatedCreatorsAndLoaders.scala b/core/src/main/scala/tech/beshu/ror/boot/SettingsRelatedCreatorsAndLoaders.scala new file mode 100644 index 0000000000..9cb5169422 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/boot/SettingsRelatedCreatorsAndLoaders.scala @@ -0,0 +1,67 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.boot + +import tech.beshu.ror.api.{MainSettingsApi, TestSettingsApi} +import tech.beshu.ror.boot.engines.{MainSettingsBasedReloadableEngine, TestSettingsBasedReloadableEngine} +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.settings.es.{EsConfigBasedRorSettings, RorCoreSettingsLoadingStrategy} +import tech.beshu.ror.settings.ror.RawRorSettingsYamlParser +import tech.beshu.ror.settings.ror.loader.{ConfigurableRetryStrategy, ForceLoadRorSettingsFromFileLoader, RetryableIndexSourceWithFileSourceFallbackRorSettingsLoader, StartingRorSettingsLoader} +import tech.beshu.ror.settings.ror.source.{MainSettingsFileSource, MainSettingsIndexSource, TestSettingsIndexSource} + +final class SettingsRelatedCreatorsAndLoaders private(val startingRorSettingsLoader: StartingRorSettingsLoader, + val creators: SettingsRelatedCreators) + +object SettingsRelatedCreatorsAndLoaders { + + def create(esConfigBasedRorSettings: EsConfigBasedRorSettings, + indexDocumentManager: IndexDocumentManager): SettingsRelatedCreatorsAndLoaders = { + val settingsIndex = esConfigBasedRorSettings.settingsSource.settingsIndex + val settingsFile = esConfigBasedRorSettings.settingsSource.settingsFile + val settingsMaxSize = esConfigBasedRorSettings.settingsSource.settingsMaxSize + val settingsYamlParser = new RawRorSettingsYamlParser(settingsMaxSize) + val mainSettingsIndexSource = MainSettingsIndexSource.create(indexDocumentManager, settingsIndex, settingsYamlParser) + val mainSettingsFileSource = MainSettingsFileSource.create(settingsFile, settingsYamlParser) + val testSettingsIndexSource = TestSettingsIndexSource.create(indexDocumentManager, settingsIndex, settingsYamlParser) + val startingSettingsLoader = esConfigBasedRorSettings.rorCoreSettingsLoadingStrategy match { + case RorCoreSettingsLoadingStrategy.ForceLoadingFromFileSettings => + new ForceLoadRorSettingsFromFileLoader(mainSettingsFileSource) + case RorCoreSettingsLoadingStrategy.LoadFromIndexWithFileFallback(retryStrategySettings, _) => + new RetryableIndexSourceWithFileSourceFallbackRorSettingsLoader( + mainSettingsIndexSource, + new ConfigurableRetryStrategy(retryStrategySettings), + mainSettingsFileSource, + testSettingsIndexSource + ) + } + new SettingsRelatedCreatorsAndLoaders( + startingSettingsLoader, + new SettingsRelatedCreators( + new MainSettingsBasedReloadableEngine.Creator(mainSettingsIndexSource), + new MainSettingsApi.Creator(settingsYamlParser, mainSettingsIndexSource, mainSettingsFileSource), + new TestSettingsBasedReloadableEngine.Creator(testSettingsIndexSource), + new TestSettingsApi.Creator(settingsYamlParser) + ) + ) + } +} + +final class SettingsRelatedCreators(val mainSettingsBasedReloadableEngineCreator: MainSettingsBasedReloadableEngine.Creator, + val mainSettingsApiCreator: MainSettingsApi.Creator, + val testSettingsBasedReloadableEngineCreator: TestSettingsBasedReloadableEngine.Creator, + val testSettingsApiCreator: TestSettingsApi.Creator) diff --git a/core/src/main/scala/tech/beshu/ror/boot/engines/BaseReloadableEngine.scala b/core/src/main/scala/tech/beshu/ror/boot/engines/BaseReloadableEngine.scala index ed8bb28961..7b104b7be1 100644 --- a/core/src/main/scala/tech/beshu/ror/boot/engines/BaseReloadableEngine.scala +++ b/core/src/main/scala/tech/beshu/ror/boot/engines/BaseReloadableEngine.scala @@ -23,15 +23,17 @@ import monix.eval.Task import monix.execution.atomic.{Atomic, AtomicAny} import monix.execution.{Cancelable, Scheduler} import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.domain.{RequestId, RorConfigurationIndex} +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.domain.RequestId import tech.beshu.ror.boot.ReadonlyRest -import tech.beshu.ror.boot.ReadonlyRest.Engine -import tech.beshu.ror.boot.RorInstance.RawConfigReloadError +import tech.beshu.ror.boot.ReadonlyRest.{Engine, StartingFailure} +import tech.beshu.ror.boot.RorInstance.RawSettingsReloadError import tech.beshu.ror.boot.engines.BaseReloadableEngine.* import tech.beshu.ror.boot.engines.BaseReloadableEngine.EngineState.NotStartedYet -import tech.beshu.ror.boot.engines.ConfigHash.* -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} +import tech.beshu.ror.boot.engines.SettingsHash.toSettingsHash import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings +import tech.beshu.ror.settings.ror.RawRorSettings import tech.beshu.ror.utils.DurationOps.* import java.time.Instant @@ -40,10 +42,10 @@ import scala.language.postfixOps private[engines] abstract class BaseReloadableEngine(val name: String, boot: ReadonlyRest, + esConfigBasedRorSettings: EsConfigBasedRorSettings, initialEngine: InitialEngine, - reloadInProgress: Semaphore[Task], - rorConfigurationIndex: RorConfigurationIndex) - (implicit environmentConfig: EnvironmentConfig, + reloadInProgress: Semaphore[Task]) + (implicit systemContext: SystemContext, scheduler: Scheduler) extends Logging { @@ -51,19 +53,19 @@ private[engines] abstract class BaseReloadableEngine(val name: String, private val currentEngine: Atomic[EngineState] = AtomicAny[EngineState]( initialEngine match { - case InitialEngine.Configured(engine, config, expirationConfig) => - logger.info(s"ROR ${name.show} engine (id=${config.hashString().show}) was initiated (${engine.core.accessControl.description.show}).") - stateFromInitial(EngineWithConfig(engine, config, expirationConfig))(RequestId(environmentConfig.uuidProvider.random.toString)) + case InitialEngine.Configured(engine, settings, expiration) => + logger.info(s"ROR ${name.show} engine (id=${settings.hashString().show}) was initiated (${engine.core.accessControl.description.show}).") + stateFromInitial(EngineWithSettings(engine, settings, expiration))(RequestId(systemContext.uuidProvider.random.toString)) case InitialEngine.NotConfigured => - EngineState.NotStartedYet(recentConfig = None, recentExpirationConfig = None) - case InitialEngine.Invalidated(config, expirationConfig) => - EngineState.NotStartedYet(recentConfig = Some(config), recentExpirationConfig = Some(expirationConfig)) + EngineState.NotStartedYet(recentSettings = None, recentExpiration = None) + case InitialEngine.Invalidated(settings, expiration) => + EngineState.NotStartedYet(recentSettings = Some(settings), recentExpiration = Some(expiration)) } ) def engine: Option[Engine] = currentEngine.get() match { case EngineState.NotStartedYet(_, _) => None - case EngineState.Working(engineWithConfig, _) => Some(engineWithConfig.engine) + case EngineState.Working(engineWithSetting, _) => Some(engineWithSetting.engine) case EngineState.Stopped => None } @@ -75,8 +77,8 @@ private[engines] abstract class BaseReloadableEngine(val name: String, _ <- Task.delay { state match { case EngineState.NotStartedYet(_, _) => - case working@EngineState.Working(engineWithConfig, _) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithConfig.config.hashString().show}) will be stopped ...") + case working@EngineState.Working(engineWithSetting, _) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithSetting.settings.hashString().show}) will be stopped ...") stopNow(working) case EngineState.Stopped => } @@ -85,29 +87,29 @@ private[engines] abstract class BaseReloadableEngine(val name: String, } } - protected def invalidate(keepPreviousConfiguration: Boolean) + protected def invalidate(keepPreviousSettings: Boolean) (implicit requestId: RequestId): Task[Option[InvalidationResult]] = { Task.delay { - val invalidationTimestamp = environmentConfig.clock.instant() + val invalidationTimestamp = systemContext.clock.instant() val previous = currentEngine.getAndTransform { case notStarted: EngineState.NotStartedYet => - if (keepPreviousConfiguration) { + if (keepPreviousSettings) { notStarted - } else { - EngineState.NotStartedYet(recentConfig = None, recentExpirationConfig = None) + } else { + EngineState.NotStartedYet(recentSettings = None, recentExpiration = None) } - case oldWorkingEngine@EngineState.Working(engineWithConfig, _) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithConfig.config.hashString().show}) will be invalidated ...") + case oldWorkingEngine@EngineState.Working(engineWithSetting, _) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithSetting.settings.hashString().show}) will be invalidated ...") stopEarly(oldWorkingEngine) - if (keepPreviousConfiguration) { + if (keepPreviousSettings) { EngineState.NotStartedYet( - recentConfig = Some(engineWithConfig.config), - recentExpirationConfig = engineWithConfig.expirationConfig.map { - recentConfig => recentConfig.copy(validTo = invalidationTimestamp) + recentSettings = Some(engineWithSetting.settings), + recentExpiration = engineWithSetting.expiration.map { + recentSettings => recentSettings.copy(validTo = invalidationTimestamp) } ) } else { - EngineState.NotStartedYet(recentConfig = None, recentExpirationConfig = None) + EngineState.NotStartedYet(recentSettings = None, recentExpiration = None) } case EngineState.Stopped => EngineState.Stopped @@ -115,9 +117,9 @@ private[engines] abstract class BaseReloadableEngine(val name: String, previous match { case _: EngineState.NotStartedYet => None - case EngineState.Working(engineWithConfig, _) => - engineWithConfig.expirationConfig.map(expirationConfig => - InvalidationResult(engineWithConfig.config, expirationConfig.copy(validTo = invalidationTimestamp)) + case EngineState.Working(engineWithSetting, _) => + engineWithSetting.expiration.map(expiration => + InvalidationResult(engineWithSetting.settings, expiration.copy(validTo = invalidationTimestamp)) ) case EngineState.Stopped => None } @@ -126,36 +128,36 @@ private[engines] abstract class BaseReloadableEngine(val name: String, protected final def currentEngineState: EngineState = currentEngine.get() - protected def reloadEngine(newConfig: RawRorConfig) - (implicit requestId: RequestId): EitherT[Task, RawConfigReloadError, Unit] = { - reloadEngineWithoutTtl(newConfig) + protected def reloadEngine(rorSettings: RawRorSettings) + (implicit requestId: RequestId): EitherT[Task, RawSettingsReloadError, Unit] = { + reloadEngineWithoutTtl(rorSettings) } - protected def reloadEngine(newConfig: RawRorConfig, - newConfigEngineTtl: PositiveFiniteDuration) - (implicit requestId: RequestId): EitherT[Task, RawConfigReloadError, ReloadResult] = { - reloadEngineWithConfiguredTtl(newConfig, UpdatedConfigExpiration.ByTtl(newConfigEngineTtl)) + protected def reloadEngine(newSettings: RawRorSettings, + newSettingsEngineTtl: PositiveFiniteDuration) + (implicit requestId: RequestId): EitherT[Task, RawSettingsReloadError, ReloadResult] = { + reloadEngineWithConfiguredTtl(newSettings, UpdatedExpiration.ByTtl(newSettingsEngineTtl)) } - protected def reloadEngine(newConfig: RawRorConfig, - newConfigExpirationTime: Instant, + protected def reloadEngine(newSettings: RawRorSettings, + newSettingsExpirationTime: Instant, configuredTtl: PositiveFiniteDuration) - (implicit requestId: RequestId): EitherT[Task, RawConfigReloadError, Unit] = { - isStillValid(newConfigExpirationTime) match { + (implicit requestId: RequestId): EitherT[Task, RawSettingsReloadError, Unit] = { + isStillValid(newSettingsExpirationTime) match { case RemainingEngineTime.Valid(_) => - reloadEngineWithConfiguredTtl(newConfig, UpdatedConfigExpiration.ToTime(newConfigExpirationTime, configuredTtl)) + reloadEngineWithConfiguredTtl(newSettings, UpdatedExpiration.ToTime(newSettingsExpirationTime, configuredTtl)) .map(_ => ()) case RemainingEngineTime.Expired => EitherT.right { Task.delay { - val newExpirationConfig = EngineExpirationConfig(ttl = configuredTtl, validTo = newConfigExpirationTime) + val newExpiration = EngineExpiration(ttl = configuredTtl, validTo = newSettingsExpirationTime) currentEngine.transform { case _: EngineState.NotStartedYet => - NotStartedYet(recentConfig = Some(newConfig), recentExpirationConfig = Some(newExpirationConfig)) + NotStartedYet(recentSettings = Some(newSettings), recentExpiration = Some(newExpiration)) case working: EngineState.Working => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${working.engineWithConfig.config.hashString().show}) will be invalidated ...") + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${working.engineWithSetting.settings.hashString().show}) will be invalidated ...") stopEarly(working) - NotStartedYet(recentConfig = Some(newConfig), recentExpirationConfig = Some(newExpirationConfig)) + NotStartedYet(recentSettings = Some(newSettings), recentExpiration = Some(newExpiration)) case EngineState.Stopped => EngineState.Stopped } @@ -164,224 +166,219 @@ private[engines] abstract class BaseReloadableEngine(val name: String, } } - private def reloadEngineWithConfiguredTtl(newConfig: RawRorConfig, - expiration: UpdatedConfigExpiration) - (implicit requestId: RequestId): EitherT[Task, RawConfigReloadError, ReloadResult] = { + private def reloadEngineWithConfiguredTtl(newSettings: RawRorSettings, + expiration: UpdatedExpiration) + (implicit requestId: RequestId): EitherT[Task, RawSettingsReloadError, ReloadResult] = { for { - engineUpdateType <- checkUpdateType(newConfig, expiration) - expirationConfig <- engineUpdateType match { - case EngineUpdateType.UpdateConfig => - runReload(newConfig, Some(expiration)).map { engine => - ReloadResult(engine.engine, engine.expirationConfig.get) + engineUpdateType <- checkUpdateType(newSettings, expiration) + expiration <- engineUpdateType match { + case EngineUpdateType.UpdateSettings => + runReload(newSettings, Some(expiration)).map { engine => + ReloadResult(engine.engine, engine.expiration.get) } - case EngineUpdateType.UpdateConfigTtl => - updateEngineExpirationConfig(expiration) - case EngineUpdateType.ConfigAndTtlUpToDate(engine, expirationConfig) => - EitherT.right[RawConfigReloadError](Task.now(ReloadResult(engine, expirationConfig))) + case EngineUpdateType.UpdateSettingsTtl => + updateEngineExpiration(expiration) + case EngineUpdateType.SettingsAndTtlUpToDate(engine, expiration) => + EitherT.right[RawSettingsReloadError](Task.now(ReloadResult(engine, expiration))) } - } yield expirationConfig + } yield expiration } - private def reloadEngineWithoutTtl(newConfig: RawRorConfig) - (implicit requestId: RequestId): EitherT[Task, RawConfigReloadError, Unit] = { + private def reloadEngineWithoutTtl(rorSettings: RawRorSettings) + (implicit requestId: RequestId): EitherT[Task, RawSettingsReloadError, Unit] = { for { - _ <- canBeReloaded(newConfig) - _ <- runReload(newConfig, None) + _ <- canBeReloaded(rorSettings) + _ <- runReload(rorSettings, None) } yield () } - private def runReload(newConfig: RawRorConfig, - configExpiration: Option[UpdatedConfigExpiration]) - (implicit requestId: RequestId): EitherT[Task, RawConfigReloadError, EngineWithConfig] = { + private def runReload(rorSettings: RawRorSettings, + expiration: Option[UpdatedExpiration]) + (implicit requestId: RequestId): EitherT[Task, RawSettingsReloadError, EngineWithSettings] = { for { - newEngineWithConfig <- reloadWith(newConfig, configExpiration) - _ <- replaceCurrentEngine(newEngineWithConfig) - } yield newEngineWithConfig + newEngineWithSettings <- reloadWith(rorSettings, expiration) + _ <- replaceCurrentEngine(newEngineWithSettings) + } yield newEngineWithSettings } - private def checkUpdateType(newConfig: RawRorConfig, - newConfigExpiration: UpdatedConfigExpiration): EitherT[Task, RawConfigReloadError, EngineUpdateType] = { + private def checkUpdateType(newSettings: RawRorSettings, + newExpiration: UpdatedExpiration): EitherT[Task, RawSettingsReloadError, EngineUpdateType] = { EitherT { Task.delay { currentEngine.get() match { case EngineState.NotStartedYet(_, _) => - Right(EngineUpdateType.UpdateConfig) - case EngineState.Working(EngineWithConfig(_, currentConfig, _), _) if currentConfig != newConfig => - Right(EngineUpdateType.UpdateConfig) - case EngineState.Working(EngineWithConfig(engine, _, engineExpirationConfig), _) => - checkIfExpirationConfigHasChanged(engine, newConfigExpiration, engineExpirationConfig) + Right(EngineUpdateType.UpdateSettings) + case EngineState.Working(EngineWithSettings(_, currentSettings, _), _) if currentSettings != newSettings => + Right(EngineUpdateType.UpdateSettings) + case EngineState.Working(EngineWithSettings(engine, _, engineExpiration), _) => + checkIfExpirationHasChanged(engine, newExpiration, engineExpiration) case EngineState.Stopped => - Left(RawConfigReloadError.RorInstanceStopped) + Left(RawSettingsReloadError.RorInstanceStopped) } } } } - private def checkIfExpirationConfigHasChanged(engine: Engine, - newConfigExpiration: UpdatedConfigExpiration, - engineExpirationConfig: Option[EngineExpirationConfig]) = { - newConfigExpiration match { - case UpdatedConfigExpiration.ByTtl(_) => - Right(EngineUpdateType.UpdateConfigTtl) - case UpdatedConfigExpiration.ToTime(validTo, configuredTtl) => - val providedExpirationConfig = EngineExpirationConfig(configuredTtl, validTo) - if (engineExpirationConfig.contains(providedExpirationConfig)) { - Right(EngineUpdateType.ConfigAndTtlUpToDate(engine, providedExpirationConfig)) + private def checkIfExpirationHasChanged(engine: Engine, + newExpiration: UpdatedExpiration, + engineExpiration: Option[EngineExpiration]) = { + newExpiration match { + case UpdatedExpiration.ByTtl(_) => + Right(EngineUpdateType.UpdateSettingsTtl) + case UpdatedExpiration.ToTime(validTo, configuredTtl) => + val providedExpiration = EngineExpiration(configuredTtl, validTo) + if (engineExpiration.contains(providedExpiration)) { + Right(EngineUpdateType.SettingsAndTtlUpToDate(engine, providedExpiration)) } else { - Right(EngineUpdateType.UpdateConfigTtl) + Right(EngineUpdateType.UpdateSettingsTtl) } } } - private def canBeReloaded(newConfig: RawRorConfig): EitherT[Task, RawConfigReloadError, Unit] = { + private def canBeReloaded(newSettings: RawRorSettings): EitherT[Task, RawSettingsReloadError, Unit] = { EitherT { Task.delay { currentEngine.get() match { case EngineState.NotStartedYet(_, _) => Right(()) - case EngineState.Working(EngineWithConfig(_, currentConfig, _), _) if currentConfig != newConfig => + case EngineState.Working(EngineWithSettings(_, currentSettings, _), _) if currentSettings != newSettings => Right(()) - case EngineState.Working(EngineWithConfig(_, currentConfig, _), _) => - Left(RawConfigReloadError.ConfigUpToDate(currentConfig)) + case EngineState.Working(EngineWithSettings(_, currentSettings, _), _) => + Left(RawSettingsReloadError.SettingsUpToDate(currentSettings)) case EngineState.Stopped => - Left(RawConfigReloadError.RorInstanceStopped) + Left(RawSettingsReloadError.RorInstanceStopped) } } } } - private def reloadWith(newConfig: RawRorConfig, - configExpiration: Option[UpdatedConfigExpiration]): EitherT[Task, RawConfigReloadError, EngineWithConfig] = EitherT { - tryToLoadRorCore(newConfig) - .map(_ - .map { engine => - EngineWithConfig( - engine = engine, - config = newConfig, - expirationConfig = configExpiration.map(engineExpirationConfig) - ) - } - .leftMap(RawConfigReloadError.ReloadingFailed.apply) - ) + private def reloadWith(rorSettings: RawRorSettings, + expiration: Option[UpdatedExpiration]): EitherT[Task, RawSettingsReloadError, EngineWithSettings] = { + EitherT(boot.loadRorEngine(rorSettings, esConfigBasedRorSettings.settingsSource.settingsIndex)) + .map { engine => + EngineWithSettings( + engine = engine, + settings = rorSettings, + expiration = expiration.map(engineExpiration) + ) + } + .leftMap(RawSettingsReloadError.ReloadingFailed.apply) } - private def engineExpirationConfig(configExpiration: UpdatedConfigExpiration) = { - configExpiration match { - case UpdatedConfigExpiration.ByTtl(ttl) => - EngineExpirationConfig(ttl = ttl, validTo = environmentConfig.clock.instant().plusMillis(ttl.value.toMillis)) - case UpdatedConfigExpiration.ToTime(validTo, configuredTtl) => - EngineExpirationConfig(ttl = configuredTtl, validTo = validTo) + private def engineExpiration(expiration: UpdatedExpiration) = { + expiration match { + case UpdatedExpiration.ByTtl(ttl) => + EngineExpiration(ttl = ttl, validTo = systemContext.clock.instant().plusMillis(ttl.value.toMillis)) + case UpdatedExpiration.ToTime(validTo, configuredTtl) => + EngineExpiration(ttl = configuredTtl, validTo = validTo) } } - private def tryToLoadRorCore(config: RawRorConfig) = - boot.loadRorCore(config, rorConfigurationIndex) - - private def replaceCurrentEngine(newEngineWithConfig: EngineWithConfig) - (implicit requestId: RequestId): EitherT[Task, RawConfigReloadError, Unit] = { + private def replaceCurrentEngine(newEngineWithSettings: EngineWithSettings) + (implicit requestId: RequestId): EitherT[Task, RawSettingsReloadError, Unit] = { EitherT { Task.delay { val oldEngineState = currentEngine.getAndTransform { case _: EngineState.NotStartedYet => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${newEngineWithConfig.config.hashString().show}) is going to be used ...") - workingStateFrom(newEngineWithConfig) + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${newEngineWithSettings.settings.hashString().show}) is going to be used ...") + workingStateFrom(newEngineWithSettings) case oldWorkingEngine@EngineState.Working(_, _) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${oldWorkingEngine.engineWithConfig.config.hashString().show}) will be replaced with engine (id=${newEngineWithConfig.config.hashString().show}) ...") + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${oldWorkingEngine.engineWithSetting.settings.hashString().show}) will be replaced with engine (id=${newEngineWithSettings.settings.hashString().show}) ...") stopEarly(oldWorkingEngine) - workingStateFrom(newEngineWithConfig) + workingStateFrom(newEngineWithSettings) case EngineState.Stopped => - logger.warn(s"[${requestId.show}] ROR ${name.show} engine (id=${newEngineWithConfig.config.hashString().show}) cannot be used because the ROR is already stopped!") - newEngineWithConfig.engine.shutdown() + logger.warn(s"[${requestId.show}] ROR ${name.show} engine (id=${newEngineWithSettings.settings.hashString().show}) cannot be used because the ROR is already stopped!") + newEngineWithSettings.engine.shutdown() EngineState.Stopped } oldEngineState match { case _: EngineState.NotStartedYet => Right(()) case EngineState.Working(_, _) => Right(()) - case EngineState.Stopped => Left(RawConfigReloadError.RorInstanceStopped) + case EngineState.Stopped => Left(RawSettingsReloadError.RorInstanceStopped) } } } } - private def workingStateFrom(engineWithConfig: EngineWithConfig) + private def workingStateFrom(engineWithSetting: EngineWithSettings) (implicit requestId: RequestId) = EngineState.Working( - engineWithConfig, - engineWithConfig.expirationConfig.map(_.ttl).map { ttl => + engineWithSetting, + engineWithSetting.expiration.map(_.ttl).map { ttl => scheduler.scheduleOnce(ttl.value) { - stopEngine(engineWithConfig) + stopEngine(engineWithSetting) } } ) - private def stopEngine(engineWithConfig: EngineWithConfig) + private def stopEngine(engineWithSetting: EngineWithSettings) (implicit requestId: RequestId): Unit = { - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithConfig.config.hashString().show}) is being stopped after TTL were reached ...") - stop(engineWithConfig) + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithSetting.settings.hashString().show}) is being stopped after TTL were reached ...") + stop(engineWithSetting) currentEngine.transform { case EngineState.NotStartedYet(_, _) => EngineState.NotStartedYet( - recentConfig = Some(engineWithConfig.config), - recentExpirationConfig = engineWithConfig.expirationConfig + recentSettings = Some(engineWithSetting.settings), + recentExpiration = engineWithSetting.expiration ) case EngineState.Working(_, _) => EngineState.NotStartedYet( - recentConfig = Some(engineWithConfig.config), - recentExpirationConfig = engineWithConfig.expirationConfig + recentSettings = Some(engineWithSetting.settings), + recentExpiration = engineWithSetting.expiration ) case EngineState.Stopped => EngineState.Stopped } } - private def stateFromInitial(engineWithConfig: EngineWithConfig) + private def stateFromInitial(engineWithSetting: EngineWithSettings) (implicit requestId: RequestId): EngineState = { - engineWithConfig.expirationConfig match { - case Some(expirationConfig) => - isStillValid(expirationConfig.validTo) match { + engineWithSetting.expiration match { + case Some(expiration) => + isStillValid(expiration.validTo) match { case RemainingEngineTime.Valid(remainingEngineTtl) => EngineState.Working( - engineWithConfig, + engineWithSetting, scheduler .scheduleOnce(remainingEngineTtl.value) { - stopEngine(engineWithConfig) + stopEngine(engineWithSetting) } .some ) case RemainingEngineTime.Expired => - stop(engineWithConfig) + stop(engineWithSetting) EngineState.NotStartedYet( - recentConfig = Some(engineWithConfig.config), - recentExpirationConfig = engineWithConfig.expirationConfig + recentSettings = Some(engineWithSetting.settings), + recentExpiration = engineWithSetting.expiration ) } case None => - EngineState.Working(engineWithConfig, scheduledShutdownJob = None) + EngineState.Working(engineWithSetting, scheduledShutdownJob = None) } } - private def updateEngineExpirationConfig(configExpiration: UpdatedConfigExpiration) - (implicit requestId: RequestId): EitherT[Task, RawConfigReloadError, ReloadResult] = { + private def updateEngineExpiration(expiration: UpdatedExpiration) + (implicit requestId: RequestId): EitherT[Task, RawSettingsReloadError, ReloadResult] = { EitherT { Task.delay { val newEngineState = currentEngine.transformAndGet { - case EngineState.NotStartedYet(recentConfig, recentExpirationConfig) => - EngineState.NotStartedYet(recentConfig, recentExpirationConfig) - case EngineState.Working(engineWithConfig, scheduledShutdownJob) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithConfig.config.hashString().show}) is being updated with new TTL ...") + case EngineState.NotStartedYet(recentSettings, recentExpiration) => + EngineState.NotStartedYet(recentSettings, recentExpiration) + case EngineState.Working(engineWithSetting, scheduledShutdownJob) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithSetting.settings.hashString().show}) is being updated with new TTL ...") scheduledShutdownJob.foreach(_.cancel()) - val engineWithNewExpirationConfig = engineWithConfig.copy(expirationConfig = Some(engineExpirationConfig(configExpiration))) - workingStateFrom(engineWithNewExpirationConfig) + val engineWithNewExpiration = engineWithSetting.copy(expiration = Some(engineExpiration(expiration))) + workingStateFrom(engineWithNewExpiration) case EngineState.Stopped => EngineState.Stopped } newEngineState match { case _: EngineState.NotStartedYet => - Left(RawConfigReloadError.ReloadingFailed(ReadonlyRest.StartingFailure("Cannot update engine TTL because engine was invalidated"))) - case EngineState.Working(engineWithConfig, _) => - Right(ReloadResult(engineWithConfig.engine, engineWithConfig.expirationConfig.get)) + Left(RawSettingsReloadError.ReloadingFailed(StartingFailure("Cannot update engine TTL because engine was invalidated"))) + case EngineState.Working(engineWithSetting, _) => + Right(ReloadResult(engineWithSetting.engine, engineWithSetting.expiration.get)) case EngineState.Stopped => - Left(RawConfigReloadError.RorInstanceStopped) + Left(RawSettingsReloadError.RorInstanceStopped) } } } @@ -391,26 +388,26 @@ private[engines] abstract class BaseReloadableEngine(val name: String, (implicit requestId: RequestId): Unit = { engineState.scheduledShutdownJob.foreach(_.cancel()) scheduler.scheduleOnce(delayOfOldEngineShutdown) { - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineState.engineWithConfig.config.hashString().show}) is being stopped early ...") - stop(engineState.engineWithConfig) + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineState.engineWithSetting.settings.hashString().show}) is being stopped early ...") + stop(engineState.engineWithSetting) } } private def stopNow(engineState: EngineState.Working) (implicit requestId: RequestId): Unit = { - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineState.engineWithConfig.config.hashString().show}) is being stopped now ...") + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineState.engineWithSetting.settings.hashString().show}) is being stopped now ...") engineState.scheduledShutdownJob.foreach(_.cancel()) - stop(engineState.engineWithConfig) + stop(engineState.engineWithSetting) } - private def stop(engineWithConfig: EngineWithConfig) + private def stop(engineWithSetting: EngineWithSettings) (implicit requestId: RequestId): Unit = { - engineWithConfig.engine.shutdown() - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithConfig.config.hashString().show}) stopped!") + engineWithSetting.engine.shutdown() + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${engineWithSetting.settings.hashString().show}) stopped!") } private def isStillValid(validTo: Instant) = { - validTo.minusMillis(environmentConfig.clock.instant().toEpochMilli) + validTo.minusMillis(systemContext.clock.instant().toEpochMilli) .toEpochMilli.millis .toRefinedPositive .map(RemainingEngineTime.Valid.apply) @@ -425,33 +422,33 @@ object BaseReloadableEngine { private[engines] object InitialEngine { case object NotConfigured extends InitialEngine final case class Configured(engine: Engine, - config: RawRorConfig, - expirationConfig: Option[EngineExpirationConfig]) extends InitialEngine - final case class Invalidated(config: RawRorConfig, - expirationConfig: EngineExpirationConfig) extends InitialEngine + settings: RawRorSettings, + expiration: Option[EngineExpiration]) extends InitialEngine + final case class Invalidated(settings: RawRorSettings, + expiration: EngineExpiration) extends InitialEngine } private[engines] final case class ReloadResult(engine: Engine, - expirationConfig: EngineExpirationConfig) + expiration: EngineExpiration) - private[engines] final case class InvalidationResult(config: RawRorConfig, - expirationConfig: EngineExpirationConfig) + private[engines] final case class InvalidationResult(settings: RawRorSettings, + expiration: EngineExpiration) - private[engines] final case class EngineExpirationConfig(ttl: PositiveFiniteDuration, - validTo: Instant) + private[engines] final case class EngineExpiration(ttl: PositiveFiniteDuration, + validTo: Instant) - private[engines] final case class EngineWithConfig(engine: Engine, - config: RawRorConfig, - expirationConfig: Option[EngineExpirationConfig]) + private[engines] final case class EngineWithSettings(engine: Engine, + settings: RawRorSettings, + expiration: Option[EngineExpiration]) private[engines] sealed trait EngineState private[engines] object EngineState { - final case class NotStartedYet(recentConfig: Option[RawRorConfig], - recentExpirationConfig: Option[EngineExpirationConfig]) + final case class NotStartedYet(recentSettings: Option[RawRorSettings], + recentExpiration: Option[EngineExpiration]) extends EngineState - final case class Working(engineWithConfig: EngineWithConfig, + final case class Working(engineWithSetting: EngineWithSettings, scheduledShutdownJob: Option[Cancelable]) extends EngineState @@ -459,11 +456,11 @@ object BaseReloadableEngine { extends EngineState } - private[BaseReloadableEngine] sealed trait UpdatedConfigExpiration - private[BaseReloadableEngine] object UpdatedConfigExpiration { - final case class ByTtl(finiteDuration: PositiveFiniteDuration) extends UpdatedConfigExpiration + private[BaseReloadableEngine] sealed trait UpdatedExpiration + private[BaseReloadableEngine] object UpdatedExpiration { + final case class ByTtl(finiteDuration: PositiveFiniteDuration) extends UpdatedExpiration final case class ToTime(validTo: Instant, - configuredTtl: PositiveFiniteDuration) extends UpdatedConfigExpiration + configuredTtl: PositiveFiniteDuration) extends UpdatedExpiration } private[BaseReloadableEngine] sealed trait RemainingEngineTime @@ -474,9 +471,9 @@ object BaseReloadableEngine { private[BaseReloadableEngine] sealed trait EngineUpdateType private[BaseReloadableEngine] object EngineUpdateType { - case object UpdateConfig extends EngineUpdateType - case object UpdateConfigTtl extends EngineUpdateType - final case class ConfigAndTtlUpToDate(engine: Engine, expirationConfig: EngineExpirationConfig) extends EngineUpdateType + case object UpdateSettings extends EngineUpdateType + case object UpdateSettingsTtl extends EngineUpdateType + final case class SettingsAndTtlUpToDate(engine: Engine, expiration: EngineExpiration) extends EngineUpdateType } private[BaseReloadableEngine] val delayOfOldEngineShutdown = 10 seconds diff --git a/core/src/main/scala/tech/beshu/ror/boot/engines/ConfigHash.scala b/core/src/main/scala/tech/beshu/ror/boot/engines/ConfigHash.scala deleted file mode 100644 index b198ee2a40..0000000000 --- a/core/src/main/scala/tech/beshu/ror/boot/engines/ConfigHash.scala +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.boot.engines - -import tech.beshu.ror.configuration.RawRorConfig -import tech.beshu.ror.utils.Hasher - -import scala.language.implicitConversions - -private[engines] class ConfigHash(val config: RawRorConfig) extends AnyVal { - - def hashString(): String = Hasher.Sha1.hashString(config.raw) -} - -private[engines] object ConfigHash { - implicit def toConfigHash(config: RawRorConfig): ConfigHash = new ConfigHash(config) -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/boot/engines/MainConfigBasedReloadableEngine.scala b/core/src/main/scala/tech/beshu/ror/boot/engines/MainConfigBasedReloadableEngine.scala deleted file mode 100644 index b4cdc9d2b9..0000000000 --- a/core/src/main/scala/tech/beshu/ror/boot/engines/MainConfigBasedReloadableEngine.scala +++ /dev/null @@ -1,134 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.boot.engines - -import tech.beshu.ror.implicits.* -import cats.data.EitherT -import cats.implicits.* -import monix.catnap.Semaphore -import monix.eval.Task -import monix.execution.Scheduler -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.boot.ReadonlyRest -import tech.beshu.ror.boot.ReadonlyRest.* -import tech.beshu.ror.boot.RorInstance.IndexConfigReloadWithUpdateError.{IndexConfigSavingError, ReloadError} -import tech.beshu.ror.boot.RorInstance.RawConfigReloadError.{ConfigUpToDate, ReloadingFailed, RorInstanceStopped} -import tech.beshu.ror.boot.RorInstance.* -import tech.beshu.ror.boot.engines.BaseReloadableEngine.InitialEngine -import tech.beshu.ror.boot.engines.ConfigHash.* -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} -import tech.beshu.ror.configuration.index.SavingIndexConfigError.CannotSaveConfig -import tech.beshu.ror.utils.ScalaOps.value - -private[boot] class MainConfigBasedReloadableEngine(boot: ReadonlyRest, - initialEngine: (Engine, RawRorConfig), - reloadInProgress: Semaphore[Task], - rorConfigurationIndex: RorConfigurationIndex) - (implicit environmentConfig: EnvironmentConfig, - scheduler: Scheduler) - extends BaseReloadableEngine( - name = "main", - boot = boot, - initialEngine = InitialEngine.Configured(engine = initialEngine._1, config = initialEngine._2, expirationConfig = None), - reloadInProgress = reloadInProgress, - rorConfigurationIndex = rorConfigurationIndex - ) { - - def forceReloadAndSave(config: RawRorConfig) - (implicit requestId: RequestId): Task[Either[IndexConfigReloadWithUpdateError, Unit]] = { - for { - _ <- Task.delay(logger.info(s"[${requestId.show}] Reloading of provided settings was forced (new engine id=${config.hashString()}) ...")) - reloadResult <- reloadInProgress.withPermit { - value { - for { - _ <- reloadEngine(config).leftMap(IndexConfigReloadWithUpdateError.ReloadError.apply) - _ <- saveConfig(config) - } yield () - } - } - _ <- Task.delay(reloadResult match { - case Right(_) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${config.hashString().show}) settings reloaded!") - case Left(ReloadError(ConfigUpToDate(oldConfig))) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${oldConfig.hashString().show}) already loaded!") - case Left(ReloadError(ReloadingFailed(StartingFailure(message, Some(ex))))) => - logger.error(s"[${requestId.show}] [${config.hashString()}] Cannot reload ROR settings - failure: ${message.show}", ex) - case Left(ReloadError(ReloadingFailed(StartingFailure(message, None)))) => - logger.error(s"[${requestId.show}] Cannot reload ROR settings - failure: ${message.show}") - case Left(ReloadError(RorInstanceStopped)) => - logger.warn(s"[${requestId.show}] ROR is being stopped! Loading main settings skipped!") - case Left(IndexConfigSavingError(CannotSaveConfig)) => - // todo: invalidate created core? - logger.warn(s"[${requestId.show}] ROR is being stopped! Loading main settings skipped!") - }) - } yield reloadResult - } - - def forceReloadFromIndex() - (implicit requestId: RequestId): Task[Either[IndexConfigReloadError, Unit]] = { - for { - _ <- Task.delay(logger.info(s"[${requestId.show}] Reloading of in-index settings was forced ...")) - reloadResult <- reloadEngineUsingIndexConfig() - _ <- Task.delay(reloadResult match { - case Right(config) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${config.hashString().show}) settings reloaded!") - case Left(IndexConfigReloadError.ReloadError(ConfigUpToDate(config))) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${config.hashString().show}) already loaded!") - case Left(IndexConfigReloadError.ReloadError(ReloadingFailed(StartingFailure(message, Some(ex))))) => - logger.error(s"[${requestId.show}] Cannot reload ROR settings - failure: ${message.show}", ex) - case Left(IndexConfigReloadError.ReloadError(ReloadingFailed(StartingFailure(message, None)))) => - logger.error(s"[${requestId.show}] Cannot reload ROR settings - failure: ${message.show}") - case Left(IndexConfigReloadError.ReloadError(RorInstanceStopped)) => - logger.warn(s"[${requestId.show}] ROR is being stopped! Loading main settings skipped!") - case Left(IndexConfigReloadError.LoadingConfigError(error)) => - logger.error(s"[${requestId.show}] Cannot reload ROR settings - failure: ${error.show}") - }) - } yield reloadResult.map(_ => ()) - } - - def reloadEngineUsingIndexConfig() - (implicit requestId: RequestId): Task[Either[IndexConfigReloadError, RawRorConfig]] = { - reloadInProgress.withPermit { - reloadEngineUsingIndexConfigWithoutPermit() - } - } - - private[boot] def reloadEngineUsingIndexConfigWithoutPermit() - (implicit requestId: RequestId): Task[Either[IndexConfigReloadError, RawRorConfig]] = { - val result = for { - newConfig <- EitherT(loadRorConfigFromIndex()) - _ <- reloadEngine(newConfig) - .leftMap(IndexConfigReloadError.ReloadError.apply) - .leftWiden[IndexConfigReloadError] - } yield newConfig - result.value - } - - private def saveConfig(newConfig: RawRorConfig): EitherT[Task, IndexConfigReloadWithUpdateError, Unit] = EitherT { - for { - saveResult <- boot.indexConfigManager.save(newConfig, rorConfigurationIndex) - } yield saveResult.left.map(IndexConfigReloadWithUpdateError.IndexConfigSavingError.apply) - } - - private def loadRorConfigFromIndex() = { - boot.indexConfigManager - .load(rorConfigurationIndex) - .map(_.left.map(IndexConfigReloadError.LoadingConfigError.apply)) - } - -} diff --git a/core/src/main/scala/tech/beshu/ror/boot/engines/MainSettingsBasedReloadableEngine.scala b/core/src/main/scala/tech/beshu/ror/boot/engines/MainSettingsBasedReloadableEngine.scala new file mode 100644 index 0000000000..ec6370e186 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/boot/engines/MainSettingsBasedReloadableEngine.scala @@ -0,0 +1,149 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.boot.engines + +import cats.data.EitherT +import monix.catnap.Semaphore +import monix.eval.Task +import monix.execution.Scheduler +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.boot.ReadonlyRest +import tech.beshu.ror.boot.ReadonlyRest.* +import tech.beshu.ror.boot.RorInstance.* +import tech.beshu.ror.boot.RorInstance.IndexSettingsReloadWithUpdateError.{IndexSettingsSavingError, ReloadError} +import tech.beshu.ror.boot.RorInstance.RawSettingsReloadError.{ReloadingFailed, RorInstanceStopped, SettingsUpToDate} +import tech.beshu.ror.boot.engines.BaseReloadableEngine.InitialEngine +import tech.beshu.ror.boot.engines.SettingsHash.toSettingsHash +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings +import tech.beshu.ror.settings.ror.MainRorSettings +import tech.beshu.ror.settings.ror.source.IndexSettingsSource.SavingError.CannotSaveSettings +import tech.beshu.ror.settings.ror.source.ReadWriteSettingsSource.SettingsSavingError.SourceSpecificError +import tech.beshu.ror.settings.ror.source.{IndexSettingsSource, MainSettingsIndexSource} +import tech.beshu.ror.utils.ScalaOps.value + +private[boot] class MainSettingsBasedReloadableEngine private(boot: ReadonlyRest, + esConfigBasedRorSettings: EsConfigBasedRorSettings, + initialEngine: MainEngine, + reloadInProgress: Semaphore[Task], + settingsSource: IndexSettingsSource[MainRorSettings]) + (implicit systemContext: SystemContext, + scheduler: Scheduler) + extends BaseReloadableEngine( + name = "main", + boot = boot, + esConfigBasedRorSettings = esConfigBasedRorSettings, + initialEngine = InitialEngine.Configured(engine = initialEngine._1, settings = initialEngine._2, expiration = None), + reloadInProgress = reloadInProgress + ) { + + def forceReloadAndSave(settings: MainRorSettings) + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadWithUpdateError, Unit]] = { + for { + _ <- Task.delay(logger.info(s"[${requestId.show}] Reloading of provided settings was forced (new engine id=${settings.rawSettings.hashString()}) ...")) + reloadResult <- reloadInProgress.withPermit { + value { + for { + _ <- reloadEngine(settings.rawSettings).leftMap(IndexSettingsReloadWithUpdateError.ReloadError.apply) + _ <- saveSettings(settings) + } yield () + } + } + _ <- Task.delay(reloadResult match { + case Right(_) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${settings.rawSettings.hashString().show}) settings reloaded!") + case Left(ReloadError(SettingsUpToDate(oldSettings))) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${oldSettings.hashString().show}) already loaded!") + case Left(ReloadError(ReloadingFailed(StartingFailure(message, Some(ex))))) => + logger.error(s"[${requestId.show}] [${settings.rawSettings.hashString()}] Cannot reload ROR settings - failure: ${message.show}", ex) + case Left(ReloadError(ReloadingFailed(StartingFailure(message, None)))) => + logger.error(s"[${requestId.show}] Cannot reload ROR settings - failure: ${message.show}") + case Left(ReloadError(RorInstanceStopped)) => + logger.warn(s"[${requestId.show}] ROR is being stopped! Loading main settings skipped!") + case Left(IndexSettingsSavingError(SourceSpecificError(CannotSaveSettings))) => + // todo: invalidate created core? + logger.warn(s"[${requestId.show}] ROR is being stopped! Loading main settings skipped!") + }) + } yield reloadResult + } + + def forceReloadFromIndex() + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadError, Unit]] = { + for { + _ <- Task.delay(logger.info(s"[${requestId.show}] Reloading of in-index settings was forced ...")) + reloadResult <- reloadEngineUsingIndexSettings() + _ <- Task.delay(reloadResult match { + case Right(settings) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${settings.rawSettings.hashString().show}) settings reloaded!") + case Left(IndexSettingsReloadError.ReloadError(SettingsUpToDate(settings))) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${settings.hashString().show}) already loaded!") + case Left(IndexSettingsReloadError.ReloadError(ReloadingFailed(StartingFailure(message, Some(ex))))) => + logger.error(s"[${requestId.show}] Cannot reload ROR settings - failure: ${message.show}", ex) + case Left(IndexSettingsReloadError.ReloadError(ReloadingFailed(StartingFailure(message, None)))) => + logger.error(s"[${requestId.show}] Cannot reload ROR settings - failure: ${message.show}") + case Left(IndexSettingsReloadError.ReloadError(RorInstanceStopped)) => + logger.warn(s"[${requestId.show}] ROR is being stopped! Loading main settings skipped!") + case Left(IndexSettingsReloadError.IndexLoadingSettingsError(error)) => + logger.error(s"[${requestId.show}] Cannot reload ROR settings - failure: ${error.show}") + }) + } yield reloadResult.map(_ => ()) + } + + private def reloadEngineUsingIndexSettings() + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadError, MainRorSettings]] = { + reloadInProgress.withPermit { + reloadEngineUsingIndexSettingsWithoutPermit() + } + } + + private[boot] def reloadEngineUsingIndexSettingsWithoutPermit() + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadError, MainRorSettings]] = { + val result = for { + newSettings <- loadRorSettingFromIndex() + _ <- reloadEngine(newSettings.rawSettings) + .leftMap(IndexSettingsReloadError.ReloadError.apply) + .leftWiden[IndexSettingsReloadError] + } yield newSettings + result.value + } + + private def loadRorSettingFromIndex() = { + EitherT(settingsSource.load()) + .leftMap(IndexSettingsReloadError.IndexLoadingSettingsError.apply) + } + + private def saveSettings(settings: MainRorSettings): EitherT[Task, IndexSettingsReloadWithUpdateError, Unit] = { + EitherT(settingsSource.save(settings)) + .leftMap(IndexSettingsReloadWithUpdateError.IndexSettingsSavingError.apply) + } + +} +object MainSettingsBasedReloadableEngine { + + final class Creator(settingsSource: MainSettingsIndexSource) { + + def create(boot: ReadonlyRest, + esConfigBasedRorSettings: EsConfigBasedRorSettings, + initialEngine: MainEngine, + reloadInProgress: Semaphore[Task]) + (implicit systemContext: SystemContext, + scheduler: Scheduler): MainSettingsBasedReloadableEngine = { + new MainSettingsBasedReloadableEngine(boot, esConfigBasedRorSettings, initialEngine, reloadInProgress, settingsSource) + } + } +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/boot/engines/SettingsHash.scala b/core/src/main/scala/tech/beshu/ror/boot/engines/SettingsHash.scala new file mode 100644 index 0000000000..4d84589139 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/boot/engines/SettingsHash.scala @@ -0,0 +1,31 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.boot.engines + +import tech.beshu.ror.settings.ror.RawRorSettings +import tech.beshu.ror.utils.Hasher + +import scala.language.implicitConversions + +private[engines] class SettingsHash(val settings: RawRorSettings) extends AnyVal { + + def hashString(): String = Hasher.Sha1.hashString(settings.rawYaml) +} + +private[engines] object SettingsHash { + implicit def toSettingsHash(config: RawRorSettings): SettingsHash = new SettingsHash(config) +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/boot/engines/TestConfigBasedReloadableEngine.scala b/core/src/main/scala/tech/beshu/ror/boot/engines/TestConfigBasedReloadableEngine.scala deleted file mode 100644 index e545bb529c..0000000000 --- a/core/src/main/scala/tech/beshu/ror/boot/engines/TestConfigBasedReloadableEngine.scala +++ /dev/null @@ -1,254 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.boot.engines - -import tech.beshu.ror.implicits.* -import cats.data.EitherT -import monix.catnap.Semaphore -import monix.eval.Task -import monix.execution.Scheduler -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.implicits.* -import tech.beshu.ror.accesscontrol.blocks.mocks.AuthServicesMocks -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.boot.ReadonlyRest -import tech.beshu.ror.boot.ReadonlyRest.{StartingFailure, TestEngine} -import tech.beshu.ror.boot.RorInstance.IndexConfigReloadWithUpdateError.{IndexConfigSavingError, ReloadError} -import tech.beshu.ror.boot.RorInstance.* -import tech.beshu.ror.boot.engines.BaseReloadableEngine.{EngineExpirationConfig, EngineState, InitialEngine} -import tech.beshu.ror.boot.engines.ConfigHash.* -import tech.beshu.ror.configuration.TestRorConfig.Present.ExpirationConfig -import tech.beshu.ror.configuration.index.SavingIndexConfigError -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig, TestRorConfig} -import tech.beshu.ror.utils.DurationOps.PositiveFiniteDuration -import tech.beshu.ror.utils.ScalaOps.value - -private[boot] class TestConfigBasedReloadableEngine private(boot: ReadonlyRest, - initialEngine: InitialEngine, - reloadInProgress: Semaphore[Task], - rorConfigurationIndex: RorConfigurationIndex) - (implicit environmentConfig: EnvironmentConfig, - scheduler: Scheduler) - extends BaseReloadableEngine( - "test", boot, initialEngine, reloadInProgress, rorConfigurationIndex - ) { - - def currentTestConfig() - (implicit requestId: RequestId): Task[TestConfig] = { - Task.delay { - currentEngineState match { - case EngineState.NotStartedYet(None, _) | EngineState.Stopped => - TestConfig.NotSet - case EngineState.NotStartedYet(Some(recentConfig), recentExpirationConfig) => - val expiration = recentExpirationConfig.getOrElse(throw new IllegalStateException("Test Config based engine should have an expiration config defined")) - TestConfig.Invalidated(recentConfig, expiration.ttl) - case EngineState.Working(engineWithConfig, _) => - val expiration = engineWithConfig.expirationConfig.getOrElse(throw new IllegalStateException("Test Config based engine should have an expiration config defined")) - TestConfig.Present(engineWithConfig.engine.core.rorConfig, engineWithConfig.config, expiration.ttl, expiration.validTo) - } - } - } - - def forceReloadTestConfigEngine(config: RawRorConfig, - ttl: PositiveFiniteDuration) - (implicit requestId: RequestId): Task[Either[IndexConfigReloadWithUpdateError, TestConfig.Present]] = { - for { - _ <- Task.delay(logger.info(s"[${requestId.show}] Reloading of ROR test settings was forced (TTL of test engine is ${ttl.show}) ...")) - reloadResult <- reloadInProgress.withPermit { - value { - for { - engineExpirationConfig <- reloadEngine(config, ttl).leftMap(IndexConfigReloadWithUpdateError.ReloadError.apply) - testRorConfig = TestRorConfig.Present( - rawConfig = config, - expiration = TestRorConfig.Present.ExpirationConfig( - ttl = engineExpirationConfig.expirationConfig.ttl, - validTo = engineExpirationConfig.expirationConfig.validTo - ), - mocks = boot.authServicesMocksProvider.currentMocks - ) - _ <- saveConfigInIndex( - newConfig = testRorConfig, - onFailure = IndexConfigReloadWithUpdateError.IndexConfigSavingError.apply - ) - .leftWiden[IndexConfigReloadWithUpdateError] - } yield TestConfig.Present( - config = engineExpirationConfig.engine.core.rorConfig, - rawConfig = config, - configuredTtl = engineExpirationConfig.expirationConfig.ttl, - validTo = engineExpirationConfig.expirationConfig.validTo - ) - } - } - _ <- Task.delay(reloadResult match { - case Right(_) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${config.hashString().show}) reloaded!") - case Left(ReloadError(RawConfigReloadError.ConfigUpToDate(oldConfig))) => - logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${oldConfig.hashString().show}) already loaded!") - case Left(ReloadError(RawConfigReloadError.ReloadingFailed(StartingFailure(message, Some(ex))))) => - logger.error(s"[${requestId.show}] Cannot reload ROR test settings - failure: ${message.show}", ex) - case Left(ReloadError(RawConfigReloadError.ReloadingFailed(StartingFailure(message, None)))) => - logger.error(s"[${requestId.show}] Cannot reload ROR test settings - failure: ${message.show}") - case Left(ReloadError(RawConfigReloadError.RorInstanceStopped)) => - logger.warn(s"[${requestId.show}] ROR is being stopped! Loading tests settings skipped!") - case Left(IndexConfigSavingError(SavingIndexConfigError.CannotSaveConfig)) => - logger.error(s"[${requestId.show}] Saving ROR test settings in index failed") - }) - } yield reloadResult - } - - def invalidateTestConfigEngine() - (implicit requestId: RequestId): Task[Either[IndexConfigInvalidationError, Unit]] = { - reloadInProgress.withPermit { - for { - invalidated <- invalidate(keepPreviousConfiguration = true) - result <- invalidated match { - case Some(invalidatedEngine) => - val config = TestRorConfig.Present( - rawConfig = invalidatedEngine.config, - expiration = ExpirationConfig( - ttl = invalidatedEngine.expirationConfig.ttl, - validTo = invalidatedEngine.expirationConfig.validTo - ), - mocks = boot.authServicesMocksProvider.currentMocks - ) - saveConfigInIndex( - newConfig = config, - onFailure = IndexConfigInvalidationError.IndexConfigSavingError.apply - ) - .leftWiden[IndexConfigInvalidationError] - .value - case None => - Task.now(Right(())) - } - } yield result - } - } - - def saveConfig(mocks: AuthServicesMocks) - (implicit requestId: RequestId): Task[Either[IndexConfigUpdateError, Unit]] = { - reloadInProgress.withPermit { - value { - for { - config <- readCurrentTestConfigForUpdate() - _ <- updateMocksProvider(mocks) - testRorConfig = TestRorConfig.Present( - rawConfig = config.rawConfig, - expiration = TestRorConfig.Present.ExpirationConfig( - ttl = config.configuredTtl, - validTo = config.validTo - ), - mocks = boot.authServicesMocksProvider.currentMocks - ) - _ <- saveConfigInIndex[IndexConfigUpdateError]( - newConfig = testRorConfig, - onFailure = IndexConfigUpdateError.IndexConfigSavingError.apply - ) - } yield () - } - } - } - - private def readCurrentTestConfigForUpdate() - (implicit requestId: RequestId): EitherT[Task, IndexConfigUpdateError, TestConfig.Present] = { - EitherT { - currentTestConfig() - .map { - case TestConfig.NotSet => - Left(IndexConfigUpdateError.TestSettingsNotSet) - case config: TestConfig.Present => - Right(config) - case _: TestConfig.Invalidated => - Left(IndexConfigUpdateError.TestSettingsInvalidated) - } - } - } - - private def updateMocksProvider[A](mocks: AuthServicesMocks): EitherT[Task, A, Unit] = { - EitherT.right(Task.delay(boot.authServicesMocksProvider.update(mocks))) - } - - private def saveConfigInIndex[A](newConfig: TestRorConfig.Present, - onFailure: SavingIndexConfigError => A): EitherT[Task, A, Unit] = { - EitherT(boot.indexTestConfigManager.save(newConfig, rorConfigurationIndex)) - .leftMap(onFailure) - } - - private[boot] def reloadEngineUsingIndexConfigWithoutPermit() - (implicit requestId: RequestId): Task[Either[IndexConfigReloadError, Unit]] = { - value { - for { - loadedConfig <- loadRorConfigFromIndex() - config <- loadedConfig match { - case TestRorConfig.NotSet => - invalidateTestConfigByIndex[IndexConfigReloadError]() - case TestRorConfig.Present(rawConfig, mocks, expiration) => - for { - _ <- reloadEngine(rawConfig, expiration.validTo, expiration.ttl) - .leftMap(IndexConfigReloadError.ReloadError.apply) - .leftWiden[IndexConfigReloadError] - _ <- updateMocksProvider[IndexConfigReloadError](mocks) - } yield () - } - } yield config - } - } - - private def loadRorConfigFromIndex(): EitherT[Task, IndexConfigReloadError, TestRorConfig] = EitherT { - boot.indexTestConfigManager - .load(rorConfigurationIndex) - .map(_.left.map(IndexConfigReloadError.LoadingConfigError.apply)) - } - - private def invalidateTestConfigByIndex[A]() - (implicit requestId: RequestId): EitherT[Task, A, Unit] = { - EitherT.right[A] { - for { - _ <- - invalidate(keepPreviousConfiguration = false) - .map { - case Some(_) => () - case None => () - } - _ <- invalidateAuthMocks() - } yield () - } - } - - private def invalidateAuthMocks(): Task[Unit] = Task.delay(boot.authServicesMocksProvider.invalidate()) - -} - -object TestConfigBasedReloadableEngine { - def create(boot: ReadonlyRest, - initialEngine: ReadonlyRest.TestEngine, - reloadInProgress: Semaphore[Task], - rorConfigurationIndex: RorConfigurationIndex) - (implicit environmentConfig: EnvironmentConfig, - scheduler: Scheduler): TestConfigBasedReloadableEngine = { - val engine = initialEngine match { - case TestEngine.NotConfigured => - InitialEngine.NotConfigured - case TestEngine.Configured(engine, config, expiration) => - InitialEngine.Configured(engine, config, Some(expirationConfig(expiration))) - case TestEngine.Invalidated(config, expiration) => - InitialEngine.Invalidated(config, expirationConfig(expiration)) - } - new TestConfigBasedReloadableEngine(boot, engine, reloadInProgress, rorConfigurationIndex) - } - - private def expirationConfig(config: TestEngine.Expiration) = EngineExpirationConfig(config.ttl, config.validTo) -} diff --git a/core/src/main/scala/tech/beshu/ror/boot/engines/TestSettingsBasedReloadableEngine.scala b/core/src/main/scala/tech/beshu/ror/boot/engines/TestSettingsBasedReloadableEngine.scala new file mode 100644 index 0000000000..191eacf41a --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/boot/engines/TestSettingsBasedReloadableEngine.scala @@ -0,0 +1,273 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.boot.engines + +import cats.data.EitherT +import monix.catnap.Semaphore +import monix.eval.Task +import monix.execution.Scheduler +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.blocks.mocks.AuthServicesMocks +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.boot.ReadonlyRest +import tech.beshu.ror.boot.ReadonlyRest.{StartingFailure, TestEngine} +import tech.beshu.ror.boot.RorInstance.* +import tech.beshu.ror.boot.RorInstance.IndexSettingsReloadWithUpdateError.{IndexSettingsSavingError, ReloadError} +import tech.beshu.ror.boot.engines.BaseReloadableEngine.{EngineExpiration, EngineState, InitialEngine} +import tech.beshu.ror.boot.engines.SettingsHash.* +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings +import tech.beshu.ror.settings.ror.TestRorSettings.Expiration +import tech.beshu.ror.settings.ror.source.IndexSettingsSource.SavingError.CannotSaveSettings +import tech.beshu.ror.settings.ror.source.IndexSettingsSource.{LoadingError, SavingError} +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource.SettingsLoadingError +import tech.beshu.ror.settings.ror.source.ReadWriteSettingsSource.SettingsSavingError +import tech.beshu.ror.settings.ror.source.ReadWriteSettingsSource.SettingsSavingError.SourceSpecificError +import tech.beshu.ror.settings.ror.source.{IndexSettingsSource, TestSettingsIndexSource} +import tech.beshu.ror.settings.ror.{RawRorSettings, TestRorSettings} +import tech.beshu.ror.utils.DurationOps.PositiveFiniteDuration +import tech.beshu.ror.utils.ScalaOps.value + +private[boot] class TestSettingsBasedReloadableEngine private(boot: ReadonlyRest, + esConfigBasedRorSettings: EsConfigBasedRorSettings, + initialEngine: InitialEngine, + reloadInProgress: Semaphore[Task], + testSettingsSource: IndexSettingsSource[TestRorSettings]) + (implicit systemContext: SystemContext, + scheduler: Scheduler) + extends BaseReloadableEngine( + name = "test", + boot = boot, + esConfigBasedRorSettings = esConfigBasedRorSettings, + initialEngine = initialEngine, + reloadInProgress = reloadInProgress + ) { + + def currentTestSettings() + (implicit requestId: RequestId): Task[TestSettings] = { + Task.delay { + currentEngineState match { + case EngineState.NotStartedYet(None, _) | EngineState.Stopped => + TestSettings.NotSet + case EngineState.NotStartedYet(Some(recentSettings), recentExpiration) => + val expiration = recentExpiration.getOrElse(throw new IllegalStateException("Test settings based engine should have an expiration defined")) + TestSettings.Invalidated(recentSettings, expiration.ttl) + case EngineState.Working(engineWithSettings, _) => + val expiration = engineWithSettings.expiration.getOrElse(throw new IllegalStateException("Test settings based engine should have an expiration defined")) + TestSettings.Present(engineWithSettings.settings, engineWithSettings.engine.core.dependencies, expiration.ttl, expiration.validTo) + } + } + } + + def forceReloadTestSettingsEngine(settings: RawRorSettings, + ttl: PositiveFiniteDuration) + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadWithUpdateError, TestSettings.Present]] = { + for { + _ <- Task.delay(logger.info(s"[${requestId.show}] Reloading of ROR test settings was forced (TTL of test engine is ${ttl.show}) ...")) + reloadResult <- reloadInProgress.withPermit { + value { + for { + engineExpiration <- reloadEngine(settings, ttl).leftMap(IndexSettingsReloadWithUpdateError.ReloadError.apply) + testRorSettings = TestRorSettings( + rawSettings = settings, + expiration = TestRorSettings.Expiration( + ttl = engineExpiration.expiration.ttl, + validTo = engineExpiration.expiration.validTo + ), + mocks = boot.authServicesMocksProvider.currentMocks + ) + _ <- saveSettingsInIndex( + newSettings = testRorSettings, + onFailure = IndexSettingsReloadWithUpdateError.IndexSettingsSavingError.apply + ) + .leftWiden[IndexSettingsReloadWithUpdateError] + } yield TestSettings.Present( + dependencies = engineExpiration.engine.core.dependencies, + rawSettings = settings, + configuredTtl = engineExpiration.expiration.ttl, + validTo = engineExpiration.expiration.validTo + ) + } + } + _ <- Task.delay(reloadResult match { + case Right(_) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${settings.hashString().show}) reloaded!") + case Left(ReloadError(RawSettingsReloadError.SettingsUpToDate(oldSettings))) => + logger.info(s"[${requestId.show}] ROR ${name.show} engine (id=${oldSettings.hashString().show}) already loaded!") + case Left(ReloadError(RawSettingsReloadError.ReloadingFailed(StartingFailure(message, Some(ex))))) => + logger.error(s"[${requestId.show}] Cannot reload ROR test settings - failure: ${message.show}", ex) + case Left(ReloadError(RawSettingsReloadError.ReloadingFailed(StartingFailure(message, None)))) => + logger.error(s"[${requestId.show}] Cannot reload ROR test settings - failure: ${message.show}") + case Left(ReloadError(RawSettingsReloadError.RorInstanceStopped)) => + logger.warn(s"[${requestId.show}] ROR is being stopped! Loading tests settings skipped!") + case Left(IndexSettingsSavingError(SourceSpecificError(CannotSaveSettings))) => + logger.error(s"[${requestId.show}] Saving ROR test settings in index failed") + }) + } yield reloadResult + } + + def invalidateTestSettingsEngine() + (implicit requestId: RequestId): Task[Either[IndexSettingsInvalidationError, Unit]] = { + reloadInProgress.withPermit { + for { + invalidated <- invalidate(keepPreviousSettings = true) + result <- invalidated match { + case Some(invalidatedEngine) => + val settings = TestRorSettings( + rawSettings = invalidatedEngine.settings, + expiration = Expiration( + ttl = invalidatedEngine.expiration.ttl, + validTo = invalidatedEngine.expiration.validTo + ), + mocks = boot.authServicesMocksProvider.currentMocks + ) + saveSettingsInIndex( + newSettings = settings, + onFailure = IndexSettingsInvalidationError.IndexSettingsSavingError.apply + ) + .leftWiden[IndexSettingsInvalidationError] + .value + case None => + Task.now(Right(())) + } + } yield result + } + } + + def saveServicesMocks(mocks: AuthServicesMocks) + (implicit requestId: RequestId): Task[Either[IndexSettingsUpdateError, Unit]] = { + reloadInProgress.withPermit { + value { + for { + settings <- readCurrentTestSettingsForUpdate() + _ <- updateMocksProvider(mocks) + testRorSettings = TestRorSettings( + rawSettings = settings.rawSettings, + expiration = TestRorSettings.Expiration( + ttl = settings.configuredTtl, + validTo = settings.validTo + ), + mocks = boot.authServicesMocksProvider.currentMocks + ) + _ <- saveSettingsInIndex[IndexSettingsUpdateError]( + newSettings = testRorSettings, + onFailure = IndexSettingsUpdateError.IndexSettingsSavingError.apply + ) + } yield () + } + } + } + + private def readCurrentTestSettingsForUpdate() + (implicit requestId: RequestId): EitherT[Task, IndexSettingsUpdateError, TestSettings.Present] = { + EitherT { + currentTestSettings() + .map { + case TestSettings.NotSet => + Left(IndexSettingsUpdateError.TestSettingsNotSet) + case settings: TestSettings.Present => + Right(settings) + case _: TestSettings.Invalidated => + Left(IndexSettingsUpdateError.TestSettingsInvalidated) + } + } + } + + private def updateMocksProvider[A](mocks: AuthServicesMocks): EitherT[Task, A, Unit] = { + EitherT.right(Task.delay(boot.authServicesMocksProvider.update(mocks))) + } + + private def saveSettingsInIndex[A](newSettings: TestRorSettings, + onFailure: SettingsSavingError[SavingError] => A): EitherT[Task, A, Unit] = { + EitherT(testSettingsSource.save(newSettings)) + .leftMap(onFailure) + } + + private[boot] def reloadEngineUsingIndexSettingsWithoutPermit() + (implicit requestId: RequestId): Task[Either[IndexSettingsReloadError, Unit]] = { + value { + for { + loadedSettings <- loadTestSettings() + result <- loadedSettings match { + case None => + invalidateTestSettingsByIndex[IndexSettingsReloadError]() + case Some(TestRorSettings(rawSettings, mocks, expiration)) => + for { + _ <- reloadEngine(rawSettings, expiration.validTo, expiration.ttl) + .leftMap(IndexSettingsReloadError.ReloadError.apply) + .leftWiden[IndexSettingsReloadError] + _ <- updateMocksProvider[IndexSettingsReloadError](mocks) + } yield () + } + } yield result + } + } + + private def loadTestSettings(): EitherT[Task, IndexSettingsReloadError, Option[TestRorSettings]] = { + EitherT(testSettingsSource.load()) + .map(Some(_)) + .leftFlatMap { + case SettingsLoadingError.SourceSpecificError(LoadingError.DocumentNotFound | LoadingError.IndexNotFound) => + EitherT.rightT(None) + case error => + EitherT.leftT(IndexSettingsReloadError.IndexLoadingSettingsError(error): IndexSettingsReloadError) + } + } + + private def invalidateTestSettingsByIndex[A]() + (implicit requestId: RequestId): EitherT[Task, A, Unit] = { + EitherT.right[A] { + for { + _ <- + invalidate(keepPreviousSettings = false) + .map { + case Some(_) => () + case None => () + } + _ <- invalidateAuthMocks() + } yield () + } + } + + private def invalidateAuthMocks(): Task[Unit] = Task.delay(boot.authServicesMocksProvider.invalidate()) + +} + +object TestSettingsBasedReloadableEngine { + + final class Creator(testSettingsSource: TestSettingsIndexSource) { + + def create(boot: ReadonlyRest, + esConfigBasedRorSettings: EsConfigBasedRorSettings, + initialEngine: ReadonlyRest.TestEngine, + reloadInProgress: Semaphore[Task]) + (implicit systemContext: SystemContext, + scheduler: Scheduler): TestSettingsBasedReloadableEngine = { + val engine = initialEngine match { + case TestEngine.NotConfigured => + InitialEngine.NotConfigured + case TestEngine.Configured(engine, settings, expiration) => + InitialEngine.Configured(engine, settings, Some(engineExpiration(expiration))) + case TestEngine.Invalidated(settings, expiration) => + InitialEngine.Invalidated(settings, engineExpiration(expiration)) + } + new TestSettingsBasedReloadableEngine(boot, esConfigBasedRorSettings, engine, reloadInProgress, testSettingsSource) + } + + private def engineExpiration(expiration: TestEngine.Expiration) = EngineExpiration(expiration.ttl, expiration.validTo) + } +} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/ConfigLoading.scala b/core/src/main/scala/tech/beshu/ror/configuration/ConfigLoading.scala deleted file mode 100644 index 62e0711062..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/ConfigLoading.scala +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import cats.free.Free -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.configuration.loader.LoadedRorConfig.{FileConfig, ForcedFileConfig, IndexConfig} -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.utils.DurationOps.NonNegativeFiniteDuration - -import java.nio.file.Path - -object ConfigLoading { - type ErrorOr[A] = LoadedRorConfig.Error Either A - type IndexErrorOr[A] = LoadedRorConfig.LoadingIndexError Either A - type LoadRorConfig[A] = Free[LoadConfigAction, A] - - sealed trait LoadConfigAction[A] - object LoadConfigAction { - final case class LoadEsConfig(env: EsEnv) - extends LoadConfigAction[ErrorOr[EsConfig]] - final case class ForceLoadRorConfigFromFile(path: Path) - extends LoadConfigAction[ErrorOr[ForcedFileConfig[RawRorConfig]]] - final case class LoadRorConfigFromFile(path: Path) - extends LoadConfigAction[ErrorOr[FileConfig[RawRorConfig]]] - final case class LoadRorConfigFromIndex(index: RorConfigurationIndex, loadingDelay: NonNegativeFiniteDuration) - extends LoadConfigAction[IndexErrorOr[IndexConfig[RawRorConfig]]] - } - - def loadRorConfigFromIndex(index: RorConfigurationIndex, - loadingDelay: NonNegativeFiniteDuration): LoadRorConfig[IndexErrorOr[IndexConfig[RawRorConfig]]] = - Free.liftF(LoadConfigAction.LoadRorConfigFromIndex(index, loadingDelay)) - - def loadRorConfigFromFile(path: Path): LoadRorConfig[ErrorOr[FileConfig[RawRorConfig]]] = - Free.liftF(LoadConfigAction.LoadRorConfigFromFile(path)) - - def loadEsConfig(env: EsEnv): LoadRorConfig[ErrorOr[EsConfig]] = - Free.liftF(LoadConfigAction.LoadEsConfig(env)) - - def forceLoadRorConfigFromFile(path: Path): LoadRorConfig[ErrorOr[ForcedFileConfig[RawRorConfig]]] = - Free.liftF(LoadConfigAction.ForceLoadRorConfigFromFile(path)) - -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/EnvironmentConfig.scala b/core/src/main/scala/tech/beshu/ror/configuration/EnvironmentConfig.scala deleted file mode 100644 index 2adb0da79a..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/EnvironmentConfig.scala +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import tech.beshu.ror.accesscontrol.blocks.variables.transformation.SupportedVariablesFunctions -import tech.beshu.ror.accesscontrol.matchers.{RandomBasedUniqueIdentifierGenerator, UniqueIdentifierGenerator} -import tech.beshu.ror.providers.* -import tech.beshu.ror.utils.js.{JsCompiler, MozillaJsCompiler} -import tech.beshu.ror.utils.yaml.RorYamlParser - -import java.time.Clock - -final class EnvironmentConfig(val clock: Clock = Clock.systemUTC(), - val envVarsProvider: EnvVarsProvider = OsEnvVarsProvider, - val propertiesProvider: PropertiesProvider = JvmPropertiesProvider, - val uniqueIdentifierGenerator: UniqueIdentifierGenerator = RandomBasedUniqueIdentifierGenerator, - val uuidProvider: UuidProvider = JavaUuidProvider, - val jsCompiler: JsCompiler = MozillaJsCompiler, - val variablesFunctions: SupportedVariablesFunctions = SupportedVariablesFunctions.default) { - - val yamlParser: RorYamlParser = new RorYamlParser(RorProperties.rorSettingsMaxSize(propertiesProvider)) -} - -object EnvironmentConfig { - - val default: EnvironmentConfig = new EnvironmentConfig() -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/EsConfig.scala b/core/src/main/scala/tech/beshu/ror/configuration/EsConfig.scala deleted file mode 100644 index 6ff32ca7a4..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/EsConfig.scala +++ /dev/null @@ -1,149 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import better.files.File -import cats.data.{EitherT, NonEmptyList} -import io.circe.Decoder -import monix.eval.Task -import tech.beshu.ror.configuration.EsConfig.LoadEsConfigError.RorSettingsInactiveWhenXpackSecurityIsEnabled.SettingsType -import tech.beshu.ror.configuration.EsConfig.LoadEsConfigError.{FileNotFound, MalformedContent, RorSettingsInactiveWhenXpackSecurityIsEnabled} -import tech.beshu.ror.configuration.EsConfig.RorEsLevelSettings -import tech.beshu.ror.configuration.FipsConfiguration.FipsMode -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.utils.yaml.YamlKeyDecoder - -import scala.language.implicitConversions - -final case class EsConfig(rorEsLevelSettings: RorEsLevelSettings, - ssl: RorSsl, - rorIndex: RorIndexNameConfiguration, - fipsConfiguration: FipsConfiguration) - -object EsConfig { - - def from(esEnv: EsEnv) - (implicit environmentConfig: EnvironmentConfig): Task[Either[LoadEsConfigError, EsConfig]] = { - val configFile = esEnv.elasticsearchConfig - (for { - _ <- EitherT.fromEither[Task](Either.cond(configFile.exists, (), FileNotFound(configFile))) - esSettings <- parse(configFile, esEnv.isOssDistribution) - ssl <- loadSslSettings(esEnv, esSettings.xpackSettings) - rorIndex <- loadRorIndexNameConfiguration(configFile) - fipsConfiguration <- loadFipsConfiguration(esEnv, esSettings.xpackSettings) - } yield EsConfig(esSettings.rorSettings, ssl, rorIndex, fipsConfiguration)).value - } - - private def parse(configFile: File, ossDistribution: Boolean) - (implicit environmentConfig: EnvironmentConfig): EitherT[Task, LoadEsConfigError, EsSettings] = { - implicit val decoder: Decoder[EsSettings] = decoders.esSettingsDecoder(ossDistribution) - EitherT.fromEither[Task]( - environmentConfig - .yamlParser - .parse(configFile) - .left.map(_.message) - .flatMap { json => - decoder - .decodeJson(json) - .left.map(_.message) - } - .left.map(MalformedContent(configFile, _)) - ) - } - - private def loadSslSettings(esEnv: EsEnv, xpackSettings: XpackSettings) - (implicit environmentConfig: EnvironmentConfig): EitherT[Task, LoadEsConfigError, RorSsl] = { - EitherT(RorSsl.load(esEnv)) - .leftMap(error => MalformedContent(esEnv.elasticsearchConfig, error.message)) - .subflatMap { rorSsl => - if(rorSsl != RorSsl.noSsl && xpackSettings.securityEnabled) { - Left(RorSettingsInactiveWhenXpackSecurityIsEnabled(SettingsType.Ssl)) - } else { - Right(rorSsl) - } - } - } - - private def loadRorIndexNameConfiguration(configFile: File) - (implicit environmentConfig: EnvironmentConfig): EitherT[Task, LoadEsConfigError, RorIndexNameConfiguration] = { - EitherT(RorIndexNameConfiguration.load(configFile).map(_.left.map(error => MalformedContent(configFile, error.message)))) - } - - private def loadFipsConfiguration(esEnv: EsEnv, xpackSettings: XpackSettings) - (implicit environmentConfig: EnvironmentConfig): EitherT[Task, LoadEsConfigError, FipsConfiguration] = { - EitherT(FipsConfiguration.load(esEnv)) - .leftMap(error => MalformedContent(esEnv.elasticsearchConfig, error.message)) - .subflatMap { fipsConfiguration => - fipsConfiguration.fipsMode match { - case FipsMode.SslOnly if xpackSettings.securityEnabled => - Left(RorSettingsInactiveWhenXpackSecurityIsEnabled(SettingsType.Fips)) - case FipsMode.NonFips | FipsMode.SslOnly => - Right(fipsConfiguration) - } - } - } - - final case class EsSettings(rorSettings: RorEsLevelSettings, - xpackSettings: XpackSettings) - final case class RorEsLevelSettings(forceLoadRorFromFile: Boolean) - final case class XpackSettings(securityEnabled: Boolean) - - sealed trait LoadEsConfigError - object LoadEsConfigError { - final case class FileNotFound(file: File) extends LoadEsConfigError - final case class MalformedContent(file: File, message: String) extends LoadEsConfigError - final case class RorSettingsInactiveWhenXpackSecurityIsEnabled(settingsType: SettingsType) extends LoadEsConfigError - object RorSettingsInactiveWhenXpackSecurityIsEnabled { - sealed trait SettingsType - object SettingsType { - case object Ssl extends SettingsType - case object Fips extends SettingsType - } - } - } - - private object decoders { - implicit val rorEsLevelSettingsDecoder: Decoder[RorEsLevelSettings] = { - YamlKeyDecoder[Boolean]( - segments = NonEmptyList.of("readonlyrest", "force_load_from_file"), - default = false - ) map RorEsLevelSettings.apply - } - - implicit val xpackSettingsDecoder: Decoder[XpackSettings] = { - val booleanDecoder = YamlKeyDecoder[Boolean]( - segments = NonEmptyList.of("xpack", "security", "enabled"), - default = true - ) - val stringDecoder = YamlKeyDecoder[String]( - segments = NonEmptyList.of("xpack", "security", "enabled"), - default = "true" - ) map { _.toBoolean } - (booleanDecoder or stringDecoder) map XpackSettings.apply - } - - implicit def esSettingsDecoder(isOssDistribution: Boolean): Decoder[EsSettings] = { - for { - rorEsLevelSettings <- Decoder[RorEsLevelSettings] - xpackSettings <- - if(isOssDistribution) Decoder.const(XpackSettings(securityEnabled = false)) - else Decoder[XpackSettings] - } yield EsSettings(rorEsLevelSettings, xpackSettings) - } - } - -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/FipsConfiguration.scala b/core/src/main/scala/tech/beshu/ror/configuration/FipsConfiguration.scala deleted file mode 100644 index 3a80e85e59..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/FipsConfiguration.scala +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import better.files.File -import io.circe.Decoder -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.configuration.FipsConfiguration.FipsMode -import tech.beshu.ror.configuration.FipsConfiguration.FipsMode.NonFips -import tech.beshu.ror.configuration.loader.FileConfigLoader -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.implicits.* - -import java.nio.file.Path - -final case class FipsConfiguration(fipsMode: FipsMode) - -object FipsConfiguration extends Logging { - - def load(esEnv: EsEnv) - (implicit environmentConfig: EnvironmentConfig): Task[Either[MalformedSettings, FipsConfiguration]] = Task { - val esConfig = esEnv.elasticsearchConfig - loadFipsConfigFromFile(esConfig) - .fold( - error => Left(error), - { - case FipsConfiguration(FipsMode.NonFips) => - logger.info(s"Cannot find FIPS configuration in ${esConfig.show} ...") - fallbackToRorConfig(esEnv.configPath) - case ssl => - Right(ssl) - } - ) - } - - private def fallbackToRorConfig(esConfigFolderPath: Path) - (implicit environmentConfig: EnvironmentConfig) = { - val rorConfig = new FileConfigLoader(esConfigFolderPath).rawConfigFile - logger.info(s"... trying: ${rorConfig.show}") - if (rorConfig.exists) { - loadFipsConfigFromFile(rorConfig) - } else { - Right(FipsConfiguration(FipsMode.NonFips)) - } - } - - private def loadFipsConfigFromFile(configFile: File) - (implicit environmentConfig: EnvironmentConfig): Either[MalformedSettings, FipsConfiguration] = { - new YamlFileBasedConfigLoader(configFile).loadConfig[FipsConfiguration](configName = "ROR FIPS Configuration") - } - - private implicit val fipsModeDecoder: Decoder[FipsMode] = { - Decoder.decodeString.emap { - case "NON_FIPS" => Right(FipsMode.NonFips) - case "SSL_ONLY" => Right(FipsMode.SslOnly) - case _ => Left("Invalid configuration option for FIPS MODE. Valid values are: NON_FIPS, SSL_ONLY") - } - } - - private implicit val fipsConfigurationDecoder: Decoder[FipsConfiguration] = { - Decoder.instance { c => - c.downField("readonlyrest") - .getOrElse[FipsMode]("fips_mode")(FipsMode.NonFips) - .map(FipsConfiguration(_)) - } - } - - sealed trait FipsMode - object FipsMode { - case object NonFips extends FipsMode - case object SslOnly extends FipsMode - } - - implicit class FipsConfigurationOps(fipsConfiguration: FipsConfiguration) { - def isSslFipsCompliant: Boolean = { - fipsConfiguration.fipsMode match { - case NonFips => false - case _ => true - } - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/RawRorConfig.scala b/core/src/main/scala/tech/beshu/ror/configuration/RawRorConfig.scala deleted file mode 100644 index 39c7f63291..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/RawRorConfig.scala +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import better.files.File -import cats.effect.Resource -import cats.{Eq, Show} -import io.circe.{Json, ParsingFailure} -import monix.eval.Task -import tech.beshu.ror.configuration.RawRorConfig.ParsingRorConfigError.{InvalidContent, MoreThanOneRorSection, NoRorSection} -import tech.beshu.ror.implicits.* - -import java.io.StringReader - -final case class RawRorConfig(configJson: Json, raw: String) - -object RawRorConfig { - - def fromFile(file: File) - (implicit environmentConfig: EnvironmentConfig): Task[Either[ParsingRorConfigError, RawRorConfig]] = { - fromString(file.contentAsString) - } - - def fromString(content: String) - (implicit environmentConfig: EnvironmentConfig): Task[Either[ParsingRorConfigError, RawRorConfig]] = { - val contentResource = Resource.make(Task(new StringReader(content))) { reader => Task(reader.close()) } - contentResource.use { reader => Task { - handleParseResult(environmentConfig.yamlParser.parse(reader)) - .map(RawRorConfig(_, content)) - }} - } - - private def handleParseResult(result: Either[ParsingFailure, Json]) = { - result - .left.map(InvalidContent.apply) - .flatMap { json => validateRorJson(json) } - } - - private def validateRorJson(json: Json) = { - json \\ "readonlyrest" match { - case Nil => Left(NoRorSection) - case _ :: Nil => Right(json) - case _ => Left(MoreThanOneRorSection) - } - } - - sealed trait ParsingRorConfigError - object ParsingRorConfigError { - case object NoRorSection extends ParsingRorConfigError - case object MoreThanOneRorSection extends ParsingRorConfigError - final case class InvalidContent(throwable: Throwable) extends ParsingRorConfigError - - implicit val show: Show[ParsingRorConfigError] = Show.show { - case NoRorSection => "Cannot find any 'readonlyrest' section in settings" - case MoreThanOneRorSection => "Only one 'readonlyrest' section is required" - case InvalidContent(ex) => s"Settings content is malformed. Details: ${ex.getMessage.show}" - } - } - - implicit val eq: Eq[RawRorConfig] = Eq.fromUniversalEquals -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/ReadonlyRestEsConfig.scala b/core/src/main/scala/tech/beshu/ror/configuration/ReadonlyRestEsConfig.scala deleted file mode 100644 index 2cfff86701..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/ReadonlyRestEsConfig.scala +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import cats.data.EitherT -import monix.eval.Task -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.utils.ScalaOps.* - -final case class ReadonlyRestEsConfig(bootConfig: RorBootConfiguration, - sslConfig: RorSsl, - fipsConfig: FipsConfiguration) - -object ReadonlyRestEsConfig { - - def load(esEnv: EsEnv) - (implicit environmentConfig: EnvironmentConfig): Task[Either[MalformedSettings, ReadonlyRestEsConfig]] = { - value { - for { - bootConfig <- EitherT(RorBootConfiguration.load(esEnv)) - sslConfig <- EitherT(RorSsl.load(esEnv)) - fipsConfig <- EitherT(FipsConfiguration.load(esEnv)) - } yield ReadonlyRestEsConfig(bootConfig, sslConfig, fipsConfig) - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/RorBootConfiguration.scala b/core/src/main/scala/tech/beshu/ror/configuration/RorBootConfiguration.scala deleted file mode 100644 index eefe0b15e1..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/RorBootConfiguration.scala +++ /dev/null @@ -1,115 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import better.files.File -import cats.data.NonEmptyList -import io.circe.Decoder -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.yaml.YamlKeyDecoder - -final case class RorBootConfiguration(rorNotStartedResponse: RorNotStartedResponse, - rorFailedToStartResponse: RorFailedToStartResponse) - -object RorBootConfiguration extends Logging { - - def load(env: EsEnv) - (implicit environmentConfig: EnvironmentConfig): Task[Either[MalformedSettings, RorBootConfiguration]] = Task { - implicit val rorBootConfigurationDecoder: Decoder[RorBootConfiguration] = Decoders.decoder - loadRorBootstrapConfig(env.elasticsearchConfig) - } - - private def loadRorBootstrapConfig(configFile: File) - (implicit decoder: Decoder[RorBootConfiguration], - environmentConfig: EnvironmentConfig) = { - new YamlFileBasedConfigLoader(configFile).loadConfig[RorBootConfiguration](configName = "ROR boot configuration") - } - - final case class RorNotStartedResponse(httpCode: RorNotStartedResponse.HttpCode) - object RorNotStartedResponse { - sealed trait HttpCode - object HttpCode { - case object `403` extends HttpCode - case object `503` extends HttpCode - } - } - - final case class RorFailedToStartResponse(httpCode: RorFailedToStartResponse.HttpCode) - object RorFailedToStartResponse { - sealed trait HttpCode - object HttpCode { - case object `403` extends HttpCode - case object `503` extends HttpCode - } - } -} - -private object Decoders extends Logging { - - object consts { - val rorSection = "readonlyrest" - val rorNotStartedResponseCode = "not_started_response_code" - val rorFailedTpStartResponseCode = "failed_to_start_response_code" - } - - def decoder: Decoder[RorBootConfiguration] = Decoder.instance { c => - for { - notStarted <- c.as[RorNotStartedResponse] - failedToStart <- c.as[RorFailedToStartResponse] - } yield RorBootConfiguration(notStarted, failedToStart) - } - - private implicit val rorNotStartedResponseDecoder: Decoder[RorNotStartedResponse] = { - val segments = NonEmptyList.of(consts.rorSection, consts.rorNotStartedResponseCode) - - implicit val httpCodeDecoder: Decoder[RorNotStartedResponse.HttpCode] = Decoder.decodeInt.emap { - case 403 => Right(RorNotStartedResponse.HttpCode.`403`) - case 503 => Right(RorNotStartedResponse.HttpCode.`503`) - case other => Left( - s"Unsupported response code [${other.show}] for ${segments.toList.mkString(".").show}. Supported response codes are: 403, 503." - ) - } - - YamlKeyDecoder[RorNotStartedResponse.HttpCode]( - segments = segments, - default = RorNotStartedResponse.HttpCode.`403` - ) - .map(RorNotStartedResponse.apply) - } - - private implicit val rorFailedToStartResponseDecoder: Decoder[RorFailedToStartResponse] = { - val segments = NonEmptyList.of(consts.rorSection, consts.rorFailedTpStartResponseCode) - - implicit val httpCodeDecoder: Decoder[RorFailedToStartResponse.HttpCode] = Decoder.decodeInt.emap { - case 403 => Right(RorFailedToStartResponse.HttpCode.`403`) - case 503 => Right(RorFailedToStartResponse.HttpCode.`503`) - case other => Left( - s"Unsupported response code [${other.show}] for ${segments.toList.mkString(".").show}. Supported response codes are: 403, 503." - ) - } - - YamlKeyDecoder[RorFailedToStartResponse.HttpCode]( - segments = segments, - default = RorFailedToStartResponse.HttpCode.`403` - ) - .map(RorFailedToStartResponse.apply) - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/RorConfig.scala b/core/src/main/scala/tech/beshu/ror/configuration/RorConfig.scala deleted file mode 100644 index 9117db6ebe..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/RorConfig.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import tech.beshu.ror.accesscontrol.audit.AuditingTool -import tech.beshu.ror.accesscontrol.blocks.ImpersonationWarning -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.LdapService -import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationService, ExternalAuthorizationService} -import tech.beshu.ror.accesscontrol.domain.{LocalUsers, RequestId} -import tech.beshu.ror.configuration.RorConfig.ImpersonationWarningsReader - -import scala.annotation.unused - -final case class RorConfig(services: RorConfig.Services, - localUsers: LocalUsers, - impersonationWarningsReader: ImpersonationWarningsReader, - auditingSettings: Option[AuditingTool.AuditSettings]) - -object RorConfig { - def disabled: RorConfig = RorConfig(RorConfig.Services.empty, LocalUsers.empty, NoOpImpersonationWarningsReader, None) - - final case class Services(authenticationServices: Seq[ExternalAuthenticationService#Id], - authorizationServices: Seq[ExternalAuthorizationService#Id], - ldaps: Seq[LdapService#Id]) - object Services { - def empty: Services = Services(Seq.empty, Seq.empty, Seq.empty) - } - - trait ImpersonationWarningsReader { - def read() - (implicit requestId: RequestId): List[ImpersonationWarning] - } - - object NoOpImpersonationWarningsReader extends ImpersonationWarningsReader { - override def read() - (implicit @unused requestId: RequestId): List[ImpersonationWarning] = List.empty - } - -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/RorIndexNameConfiguration.scala b/core/src/main/scala/tech/beshu/ror/configuration/RorIndexNameConfiguration.scala deleted file mode 100644 index 6e5008612a..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/RorIndexNameConfiguration.scala +++ /dev/null @@ -1,57 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import better.files.File -import cats.data.NonEmptyList -import eu.timepit.refined.types.string.NonEmptyString -import io.circe.Decoder -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.domain.{IndexName, RorConfigurationIndex} -import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers.* -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.utils.RefinedUtils.* -import tech.beshu.ror.utils.yaml.YamlKeyDecoder - -final case class RorIndexNameConfiguration(index: RorConfigurationIndex) - -object RorIndexNameConfiguration extends Logging { - - private val defaultIndexName = IndexName.Full(nes(".readonlyrest")) - - def load(esEnv: EsEnv) - (implicit environmentConfig: EnvironmentConfig): Task[Either[MalformedSettings, RorIndexNameConfiguration]] = { - load(esEnv.elasticsearchConfig) - } - - def load(esConfig: File) - (implicit environmentConfig: EnvironmentConfig): Task[Either[MalformedSettings, RorIndexNameConfiguration]] = Task { - new YamlFileBasedConfigLoader(esConfig).loadConfig[RorIndexNameConfiguration](configName = "ROR index configuration") - } - - private implicit val rorIndexNameConfigurationDecoder: Decoder[RorIndexNameConfiguration] = { - implicit val indexNameDecoder: Decoder[IndexName.Full] = Decoder[NonEmptyString].map(IndexName.Full.apply) - - YamlKeyDecoder[IndexName.Full]( - segments = NonEmptyList.of("readonlyrest", "settings_index"), - default = defaultIndexName - ) - .map(RorConfigurationIndex.apply) - .map(RorIndexNameConfiguration.apply) - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/RorProperties.scala b/core/src/main/scala/tech/beshu/ror/configuration/RorProperties.scala deleted file mode 100644 index 84d206b844..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/RorProperties.scala +++ /dev/null @@ -1,194 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import better.files.File -import cats.Show -import eu.timepit.refined.api.Refined -import eu.timepit.refined.numeric.NonNegative -import eu.timepit.refined.types.string.NonEmptyString -import org.apache.logging.log4j.scala.Logging -import squants.information.{Information, Megabytes} -import tech.beshu.ror.implicits.* -import tech.beshu.ror.providers.PropertiesProvider -import tech.beshu.ror.providers.PropertiesProvider.PropName -import tech.beshu.ror.utils.DurationOps.* -import tech.beshu.ror.utils.RefinedUtils.* - -import java.util.concurrent.TimeUnit -import scala.concurrent.duration.* -import scala.language.postfixOps -import scala.util.{Failure, Success, Try} - -object RorProperties extends Logging { - - object defaults { - val refreshInterval: PositiveFiniteDuration = (5 second).toRefinedPositiveUnsafe - val loadingDelay: NonNegativeFiniteDuration = (5 second).toRefinedNonNegativeUnsafe - val loadingAttemptsCount: Int Refined NonNegative = Refined.unsafeApply(5) - val loadingAttemptsInterval: NonNegativeFiniteDuration = (5 second).toRefinedNonNegativeUnsafe - val rorSettingsMaxSize: Information = Megabytes(3) - } - - object keys { - val rorSettingsFilePath: NonEmptyString = nes("com.readonlyrest.settings.file.path") - val rorSettingsRefreshInterval: NonEmptyString = nes("com.readonlyrest.settings.refresh.interval") - val startupIndexLoadingDelay: NonEmptyString = nes("com.readonlyrest.settings.loading.delay") - val startupIndexLoadingAttemptsInterval: NonEmptyString = nes("com.readonlyrest.settings.loading.attempts.interval") - val startupIndexLoadingAttemptsCount: NonEmptyString = nes("com.readonlyrest.settings.loading.attempts.count") - val rorSettingsMaxSize: NonEmptyString = nes("com.readonlyrest.settings.maxSize") - } - - def rorSettingsCustomFile(implicit propertiesProvider: PropertiesProvider): Option[File] = - propertiesProvider - .getProperty(PropName(keys.rorSettingsFilePath)) - .map(File(_)) - - def rorIndexSettingsReloadInterval(implicit propertiesProvider: PropertiesProvider): RefreshInterval = - getProperty( - keys.rorSettingsRefreshInterval, - str => toRefreshInterval(str), - RefreshInterval.Enabled(defaults.refreshInterval) - ) - - def atStartupRorIndexSettingsLoadingAttemptsInterval(implicit propertiesProvider: PropertiesProvider): LoadingAttemptsInterval = - getProperty( - keys.startupIndexLoadingAttemptsInterval, - str => toLoadingAttemptsInterval(str), - LoadingAttemptsInterval(defaults.loadingAttemptsInterval) - ) - - def atStartupRorIndexSettingsLoadingAttemptsCount(implicit propertiesProvider: PropertiesProvider): LoadingAttemptsCount = - getProperty( - keys.startupIndexLoadingAttemptsCount, - str => toLoadingAttempts(str), - LoadingAttemptsCount(defaults.loadingAttemptsCount) - ) - - def atStartupRorIndexSettingLoadingDelay(implicit propertiesProvider: PropertiesProvider): LoadingDelay = - getProperty( - keys.startupIndexLoadingDelay, - str => toLoadingDelay(str), - LoadingDelay(defaults.loadingDelay) - ) - - def rorSettingsMaxSize(implicit propertiesProvider: PropertiesProvider): Information = - getProperty( - keys.rorSettingsMaxSize, - Information.parseString, - defaults.rorSettingsMaxSize - ) - - private def getProperty[T: Show](name: NonEmptyString, fromString: String => Try[T], default: T) - (implicit provider: PropertiesProvider) = { - getPropertyOf( - name, - fromString, - { - logger.info(s"No '${name.show}' property found. Using default: ${default.show}") - default - } - ) - } - - private def getPropertyOf[T](name: NonEmptyString, fromString: String => Try[T], default: => T) - (implicit provider: PropertiesProvider): T = { - provider - .getProperty(PropName(name)) - .map { stringValue => - fromString(stringValue) match { - case Success(value) => value - case Failure(ex) => throw new IllegalArgumentException(s"Invalid format of parameter ${name.show}=${stringValue.show}", ex) - } - } - .getOrElse { - default - } - } - - private def toRefreshInterval(value: String): Try[RefreshInterval] = toPositiveFiniteDuration(value).map { - case Some(value) => RefreshInterval.Enabled(value) - case None => RefreshInterval.Disabled - } - - private def toLoadingAttemptsInterval(value: String): Try[LoadingAttemptsInterval] = - toNonNegativeFiniteDuration(value).map(LoadingAttemptsInterval.apply) - - private def toLoadingDelay(value: String): Try[LoadingDelay] = toNonNegativeFiniteDuration(value).map(LoadingDelay.apply) - - private def toLoadingAttempts(value: String): Try[LoadingAttemptsCount] = toNonNegativeInt(value).map(LoadingAttemptsCount.apply) - - private def toPositiveFiniteDuration(value: String): Try[Option[PositiveFiniteDuration]] = Try { - durationFrom(value) match { - case d if d == Duration.Zero => None - case d => Some(d.toRefinedPositiveUnsafe) - } - } - - private def toNonNegativeFiniteDuration(value: String): Try[NonNegativeFiniteDuration] = Try { - durationFrom(value).toRefinedNonNegativeUnsafe - } - - private def durationFrom(value: String) = { - Try(value.toLong) match { - case Success(seconds) => FiniteDuration(seconds, TimeUnit.SECONDS) - case Failure(_) => Duration(value) - } - } - - private def toNonNegativeInt(value: String): Try[Int Refined NonNegative] = Try { - Try(Integer.valueOf(value)) match { - case Success(int) if int >= 0 => Refined.unsafeApply(int) - case Success(_) | Failure(_) => throw new IllegalArgumentException(s"Cannot convert '${value.show}' to non-negative integer") - } - } - - final case class LoadingDelay(value: NonNegativeFiniteDuration) extends AnyVal - object LoadingDelay { - def unsafeFrom(value: FiniteDuration): LoadingDelay = LoadingDelay(value.toRefinedNonNegativeUnsafe) - - implicit val show: Show[LoadingDelay] = Show[FiniteDuration].contramap(_.value.value) - } - - sealed trait RefreshInterval - object RefreshInterval { - case object Disabled extends RefreshInterval - - final case class Enabled(interval: PositiveFiniteDuration) extends RefreshInterval - - implicit val show: Show[RefreshInterval] = Show.show { - case Disabled => "0 sec" - case Enabled(interval) => interval.value.toString() - } - } - - final case class LoadingAttemptsCount(value: Int Refined NonNegative) extends AnyVal - object LoadingAttemptsCount { - def unsafeFrom(value: Int): LoadingAttemptsCount = LoadingAttemptsCount(Refined.unsafeApply(value)) - - val zero: LoadingAttemptsCount = LoadingAttemptsCount.unsafeFrom(0) - - implicit val show: Show[LoadingAttemptsCount] = Show[Int].contramap(_.value.value) - } - - final case class LoadingAttemptsInterval(value: NonNegativeFiniteDuration) extends AnyVal - object LoadingAttemptsInterval { - def unsafeFrom(value: FiniteDuration): LoadingAttemptsInterval = LoadingAttemptsInterval(value.toRefinedNonNegativeUnsafe) - - implicit val show: Show[LoadingAttemptsInterval] = Show[FiniteDuration].contramap(_.value.value) - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/SslConfiguration.scala b/core/src/main/scala/tech/beshu/ror/configuration/SslConfiguration.scala deleted file mode 100644 index 9ab59c953c..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/SslConfiguration.scala +++ /dev/null @@ -1,318 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import better.files.* -import io.circe.{Decoder, DecodingFailure, HCursor} -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers -import tech.beshu.ror.configuration.SslConfiguration.{ExternalSslConfiguration, InternodeSslConfiguration} -import tech.beshu.ror.configuration.loader.FileConfigLoader -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.SSLCertHelper - -import java.io.File as JFile -import java.nio.file.{Path, Paths} - -final case class RorSsl(externalSsl: Option[ExternalSslConfiguration], - interNodeSsl: Option[InternodeSslConfiguration]) - -object RorSsl extends Logging { - - val noSsl: RorSsl = RorSsl(None, None) - - def load(esEnv: EsEnv) - (implicit environmentConfig: EnvironmentConfig): Task[Either[MalformedSettings, RorSsl]] = Task { - implicit val sslDecoder: Decoder[RorSsl] = SslDecoders.rorSslDecoder(esEnv.configPath) - val esConfig = esEnv.elasticsearchConfig - loadSslConfigFromFile(esConfig) - .fold( - error => Left(error), - { - case RorSsl(None, None) => - logger.info(s"Cannot find SSL configuration in ${esConfig.show} ...") - fallbackToRorConfig(esEnv.configPath) - case ssl => - Right(ssl) - } - ) - } - - private def fallbackToRorConfig(esConfigFolderPath: Path) - (implicit rorSslDecoder: Decoder[RorSsl], - environmentConfig: EnvironmentConfig) = { - val rorConfig = new FileConfigLoader(esConfigFolderPath).rawConfigFile - logger.info(s"... trying: ${rorConfig.show}") - if (rorConfig.exists) { - loadSslConfigFromFile(rorConfig) - } else { - Right(RorSsl.noSsl) - } - } - - private def loadSslConfigFromFile(configFile: File) - (implicit rorSslDecoder: Decoder[RorSsl], - environmentConfig: EnvironmentConfig) = { - new YamlFileBasedConfigLoader(configFile).loadConfig[RorSsl](configName = "ROR SSL configuration") - } -} - -sealed trait SslConfiguration { - def serverCertificateConfiguration: SslConfiguration.ServerCertificateConfiguration - def clientCertificateConfiguration: Option[SslConfiguration.ClientCertificateConfiguration] - def allowedProtocols: Set[SslConfiguration.Protocol] - def allowedCiphers: Set[SslConfiguration.Cipher] - def clientAuthenticationEnabled: Boolean - def certificateVerificationEnabled: Boolean -} - -object SslConfiguration { - - final case class KeystorePassword(value: String) - final case class KeystoreFile(value: JFile) - final case class TruststorePassword(value: String) - final case class TruststoreFile(value: JFile) - final case class ServerCertificateKeyFile(value: JFile) - final case class ServerCertificateFile(value: JFile) - final case class ClientTrustedCertificateFile(value: JFile) - final case class KeyPass(value: String) - final case class KeyAlias(value: String) - final case class Cipher(value: String) - final case class Protocol(value: String) - - sealed trait ServerCertificateConfiguration - object ServerCertificateConfiguration { - final case class KeystoreBasedConfiguration(keystoreFile: KeystoreFile, - keystorePassword: Option[KeystorePassword], - keyAlias: Option[KeyAlias], - keyPass: Option[KeyPass]) extends ServerCertificateConfiguration - final case class FileBasedConfiguration(serverCertificateKeyFile: ServerCertificateKeyFile, - serverCertificateFile: ServerCertificateFile) extends ServerCertificateConfiguration - } - - sealed trait ClientCertificateConfiguration - object ClientCertificateConfiguration { - final case class TruststoreBasedConfiguration(truststoreFile: TruststoreFile, - truststorePassword: Option[TruststorePassword]) extends ClientCertificateConfiguration - final case class FileBasedConfiguration(clientTrustedCertificateFile: ClientTrustedCertificateFile) extends ClientCertificateConfiguration - } - - final case class ExternalSslConfiguration(serverCertificateConfiguration: ServerCertificateConfiguration, - clientCertificateConfiguration: Option[ClientCertificateConfiguration], - allowedProtocols: Set[SslConfiguration.Protocol], - allowedCiphers: Set[SslConfiguration.Cipher], - clientAuthenticationEnabled: Boolean) - extends SslConfiguration { - - val certificateVerificationEnabled: Boolean = false - } - - final case class InternodeSslConfiguration(serverCertificateConfiguration: ServerCertificateConfiguration, - clientCertificateConfiguration: Option[ClientCertificateConfiguration], - allowedProtocols: Set[SslConfiguration.Protocol], - allowedCiphers: Set[SslConfiguration.Cipher], - clientAuthenticationEnabled: Boolean, - certificateVerificationEnabled: Boolean, - hostnameVerificationEnabled: Boolean) - extends SslConfiguration -} - -private object SslDecoders extends Logging { - import tech.beshu.ror.configuration.SslConfiguration.* - - object consts { - val rorSection = "readonlyrest" - val externalSsl = "ssl" - val internodeSsl = "ssl_internode" - val keystoreFile = "keystore_file" - val keystorePass = "keystore_pass" - val truststoreFile = "truststore_file" - val truststorePass = "truststore_pass" - val keyPass = "key_pass" - val keyAlias = "key_alias" - val allowedCiphers = "allowed_ciphers" - val allowedProtocols = "allowed_protocols" - val certificateVerification = "certificate_verification" - val hostnameVerification = "hostname_verification" - val clientAuthentication = "client_authentication" - val verification = "verification" - val enable = "enable" - val serverCertificateKeyFile = "server_certificate_key_file" - val serverCertificateFile = "server_certificate_file" - val clientTrustedCertificateFile = "client_trusted_certificate_file" - } - - final case class CommonSslProperties(serverCertificateConfiguration: ServerCertificateConfiguration, - clientCertificateConfiguration: Option[ClientCertificateConfiguration], - allowedProtocols: Set[SslConfiguration.Protocol], - allowedCiphers: Set[SslConfiguration.Cipher], - clientAuthentication: Option[Boolean]) - - - private implicit val keystorePasswordDecoder: Decoder[KeystorePassword] = DecoderHelpers.decodeStringLike.map(KeystorePassword.apply) - private implicit val truststorePasswordDecoder: Decoder[TruststorePassword] = DecoderHelpers.decodeStringLike.map(TruststorePassword.apply) - private implicit val keyPassDecoder: Decoder[KeyPass] = DecoderHelpers.decodeStringLike.map(KeyPass.apply) - private implicit val keyAliasDecoder: Decoder[KeyAlias] = DecoderHelpers.decodeStringLike.map(KeyAlias.apply) - private implicit val cipherDecoder: Decoder[Cipher] = DecoderHelpers.decodeStringLike.map(Cipher.apply) - private implicit val protocolDecoder: Decoder[Protocol] = DecoderHelpers.decodeStringLike.map(Protocol.apply) - - private def clientCertificateConfigurationDecoder(basePath: Path): Decoder[Option[ClientCertificateConfiguration]] = { - val jFileDecoder: Decoder[JFile] = fileDecoder(basePath) - implicit val truststoreFileDecoder = jFileDecoder.map(TruststoreFile.apply) - implicit val clientTrustedCertificateFileDecoder = jFileDecoder.map(ClientTrustedCertificateFile.apply) - - val truststoreBasedClientCertificateConfigurationDecoder: Decoder[ClientCertificateConfiguration] = - Decoder.forProduct2(consts.truststoreFile, consts.truststorePass)(ClientCertificateConfiguration.TruststoreBasedConfiguration.apply) - val fileBasedClientCertificateConfigurationDecoder: Decoder[ClientCertificateConfiguration] = - Decoder.forProduct1(consts.clientTrustedCertificateFile)(ClientCertificateConfiguration.FileBasedConfiguration.apply) - Decoder.instance { c => - val truststoreBasedKeys = Set(consts.truststoreFile, consts.truststorePass) - val fileBasedKeys = Set(consts.clientTrustedCertificateFile) - val presentKeys = c.keys.fold[Set[String]](Set.empty)(_.toSet) - if (presentKeys.intersect(truststoreBasedKeys).nonEmpty && presentKeys.intersect(fileBasedKeys).nonEmpty) { - val errorMessage = s"Field sets [${fileBasedKeys.show}] and [${truststoreBasedKeys.show}] could not be present in the same configuration section" - logger.error(errorMessage) - Left(DecodingFailure(errorMessage, List.empty)) - } else if (presentKeys.intersect(truststoreBasedKeys).nonEmpty) { - truststoreBasedClientCertificateConfigurationDecoder(c) - .map(Option.apply) - } else if (presentKeys.intersect(fileBasedKeys).nonEmpty) { - if (SSLCertHelper.isPEMHandlingAvailable) { - fileBasedClientCertificateConfigurationDecoder(c) - .map(Option.apply) - } else { - val errorMessage = "PEM File Handling is not available in your current deployment of Elasticsearch" - logger.error(errorMessage) - Left(DecodingFailure(errorMessage, List.empty)) - } - } else { - Right(None) - } - } - } - - private def serverCertificateConfigurationDecoder(basePath: Path): Decoder[ServerCertificateConfiguration] = { - val jFileDecoder: Decoder[JFile] = fileDecoder(basePath) - implicit val keystoreFileDecoder = jFileDecoder.map(KeystoreFile.apply) - implicit val serverCertificateFileDecoder = jFileDecoder.map(ServerCertificateFile.apply) - implicit val serverCertificateKeyFileDecoder = jFileDecoder.map(ServerCertificateKeyFile.apply) - val keystoreBasedServerCertificateConfigurationDecoder: Decoder[ServerCertificateConfiguration] = - Decoder.forProduct4(consts.keystoreFile, consts.keystorePass, consts.keyAlias, consts.keyPass)(ServerCertificateConfiguration.KeystoreBasedConfiguration.apply) - val fileBasedServerCertificateConfigurationDecoder: Decoder[ServerCertificateConfiguration] = - Decoder.forProduct2(consts.serverCertificateKeyFile, consts.serverCertificateFile)(ServerCertificateConfiguration.FileBasedConfiguration.apply) - Decoder.instance { c => - val keystoreBasedKeys = Set(consts.keystoreFile, consts.keystorePass, consts.keyPass, consts.keyAlias) - val fileBasedKeys = Set(consts.serverCertificateKeyFile, consts.serverCertificateFile) - val presentKeys = c.keys.fold[Set[String]](Set.empty)(_.toSet) - if (presentKeys.intersect(keystoreBasedKeys).nonEmpty && presentKeys.intersect(fileBasedKeys).nonEmpty) { - val errorMessage = s"Field sets [${fileBasedKeys.show}] and [${keystoreBasedKeys.show}] could not be present in the same configuration section" - logger.error(errorMessage) - Left(DecodingFailure(errorMessage, List.empty)) - } else if (presentKeys.intersect(keystoreBasedKeys).nonEmpty) { - keystoreBasedServerCertificateConfigurationDecoder(c) - } else if (presentKeys.intersect(fileBasedKeys).nonEmpty) { - if (SSLCertHelper.isPEMHandlingAvailable) { - fileBasedServerCertificateConfigurationDecoder(c) - } else { - val errorMessage = "PEM File Handling is not available in your current deployment of Elasticsearch" - logger.error(errorMessage) - Left(DecodingFailure(errorMessage, List.empty)) - } - } else { - val errorMessage = "There was no SSL configuration present for server" - logger.error(errorMessage) - Left(DecodingFailure(errorMessage, List.empty)) - } - } - } - - def rorSslDecoder(basePath: Path): Decoder[RorSsl] = Decoder.instance { c => - implicit val internodeSslConfigDecoder: Decoder[Option[InternodeSslConfiguration]] = sslInternodeConfigurationDecoder(basePath) - implicit val externalSslConfigDecoder: Decoder[Option[ExternalSslConfiguration]] = sslExternalConfigurationDecoder(basePath) - for { - interNodeSsl <- c.downField(consts.rorSection).downField(consts.internodeSsl).as[Option[Option[InternodeSslConfiguration]]] - externalSsl <- c.downField(consts.rorSection).downField(consts.externalSsl).as[Option[Option[ExternalSslConfiguration]]] - } yield RorSsl(externalSsl.flatten, interNodeSsl.flatten) - } - - private def sslInternodeConfigurationDecoder(basePath: Path): Decoder[Option[InternodeSslConfiguration]] = Decoder.instance { c => - whenEnabled(c) { - for { - certificateVerification <- c.downField(consts.certificateVerification).as[Option[Boolean]] - hostnameVerification <- c.downField(consts.hostnameVerification).as[Option[Boolean]] - verification <- c.downField(consts.verification).as[Option[Boolean]] - sslCommonProperties <- sslCommonPropertiesDecoder(basePath, c) - } yield - InternodeSslConfiguration( - serverCertificateConfiguration = sslCommonProperties.serverCertificateConfiguration, - clientCertificateConfiguration = sslCommonProperties.clientCertificateConfiguration, - allowedProtocols = sslCommonProperties.allowedProtocols, - allowedCiphers = sslCommonProperties.allowedCiphers, - clientAuthenticationEnabled = sslCommonProperties.clientAuthentication.getOrElse(false), - certificateVerificationEnabled = certificateVerification.orElse(verification).getOrElse(false), - hostnameVerificationEnabled = hostnameVerification.getOrElse(false) - ) - } - } - - private def sslExternalConfigurationDecoder(basePath: Path): Decoder[Option[ExternalSslConfiguration]] = Decoder.instance { c => - whenEnabled(c) { - for { - verification <- c.downField(consts.verification).as[Option[Boolean]] - sslCommonProperties <- sslCommonPropertiesDecoder(basePath, c) - } yield - ExternalSslConfiguration( - serverCertificateConfiguration = sslCommonProperties.serverCertificateConfiguration, - clientCertificateConfiguration = sslCommonProperties.clientCertificateConfiguration, - allowedProtocols = sslCommonProperties.allowedProtocols, - allowedCiphers = sslCommonProperties.allowedCiphers, - clientAuthenticationEnabled = sslCommonProperties.clientAuthentication.orElse(verification).getOrElse(false) - ) - } - } - - private def sslCommonPropertiesDecoder(basePath: Path, c: HCursor) = { - for { - ciphers <- c.downField(consts.allowedCiphers).as[Option[Set[Cipher]]] - protocols <- c.downField(consts.allowedProtocols).as[Option[Set[Protocol]]] - clientAuthentication <- c.downField(consts.clientAuthentication).as[Option[Boolean]] - serverCertificateConfiguration <- serverCertificateConfigurationDecoder(basePath).apply(c) - clientCertificateConfiguration <- clientCertificateConfigurationDecoder(basePath).apply(c) - } yield - CommonSslProperties( - serverCertificateConfiguration = serverCertificateConfiguration, - clientCertificateConfiguration = clientCertificateConfiguration, - allowedProtocols = protocols.getOrElse(Set.empty[Protocol]), - allowedCiphers = ciphers.getOrElse(Set.empty[Cipher]), - clientAuthentication = clientAuthentication, - ) - } - - private def whenEnabled[T <: SslConfiguration](cursor: HCursor)(decoding: => Either[DecodingFailure, T]) = { - for { - isEnabled <- cursor.downField(consts.enable).as[Option[Boolean]] - result <- if (isEnabled.getOrElse(true)) decoding.map(Some.apply) else Right(None) - } yield result - } - - private def fileDecoder(basePath: Path): Decoder[JFile] = - Decoder - .decodeString - .map { str => basePath.resolve(Paths.get(str)).toFile } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/TestConfigLoading.scala b/core/src/main/scala/tech/beshu/ror/configuration/TestConfigLoading.scala deleted file mode 100644 index 5b95cc66ad..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/TestConfigLoading.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import cats.free.Free -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.configuration.loader.LoadedTestRorConfig -import tech.beshu.ror.utils.DurationOps.NonNegativeFiniteDuration - -object TestConfigLoading { - type IndexErrorOr[A] = LoadedTestRorConfig.LoadingIndexError Either A - type LoadTestRorConfig[A] = Free[LoadTestConfigAction, A] - - sealed trait LoadTestConfigAction[A] - object LoadTestConfigAction { - final case class LoadRorConfigFromIndex(index: RorConfigurationIndex, loadingDelay: NonNegativeFiniteDuration) - extends LoadTestConfigAction[IndexErrorOr[LoadedTestRorConfig.IndexConfig[TestRorConfig]]] - } - - def loadRorConfigFromIndex(index: RorConfigurationIndex, - loadingDelay: NonNegativeFiniteDuration): LoadTestRorConfig[IndexErrorOr[LoadedTestRorConfig.IndexConfig[TestRorConfig]]] = - Free.liftF(LoadTestConfigAction.LoadRorConfigFromIndex(index, loadingDelay)) - -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/TestRorConfig.scala b/core/src/main/scala/tech/beshu/ror/configuration/TestRorConfig.scala deleted file mode 100644 index 97a5a9c416..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/TestRorConfig.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import tech.beshu.ror.accesscontrol.blocks.mocks.AuthServicesMocks -import tech.beshu.ror.utils.DurationOps.PositiveFiniteDuration - -import java.time.{Clock, Instant} - -sealed trait TestRorConfig -object TestRorConfig { - - case object NotSet extends TestRorConfig - final case class Present(rawConfig: RawRorConfig, - mocks: AuthServicesMocks, - expiration: Present.ExpirationConfig) extends TestRorConfig { - def isExpired(clock: Clock): Boolean = { - expiration.validTo.isBefore(clock.instant()) - } - } - - object Present { - final case class ExpirationConfig(ttl: PositiveFiniteDuration, validTo: Instant) - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/YamlFileBasedConfigLoader.scala b/core/src/main/scala/tech/beshu/ror/configuration/YamlFileBasedConfigLoader.scala deleted file mode 100644 index ef4814ec02..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/YamlFileBasedConfigLoader.scala +++ /dev/null @@ -1,67 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import better.files.File -import io.circe.{Decoder, DecodingFailure, Json} -import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler -import tech.beshu.ror.accesscontrol.factory.JsonConfigStaticVariableResolver -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.yaml -import tech.beshu.ror.utils.yaml.YamlOps.jsonWithOneLinerKeysToRegularJson - -final class YamlFileBasedConfigLoader(file: File) - (implicit environmentConfig: EnvironmentConfig) { - - private val jsonConfigResolver = new JsonConfigStaticVariableResolver( - environmentConfig.envVarsProvider, - TransformationCompiler.withoutAliases(environmentConfig.variablesFunctions) - ) - - def loadConfig[CONFIG: Decoder](configName: String): Either[MalformedSettings, CONFIG] = { - loadedConfigJson - .flatMap { json => - implicitly[Decoder[CONFIG]] - .decodeJson(json) - .left.map(e => MalformedSettings(s"Cannot load ${configName.show} from file ${file.pathAsString.show}. Cause: ${prettyCause(e).show}")) - } - } - - private lazy val loadedConfigJson: Either[MalformedSettings, Json] = { - file.fileReader { reader => - environmentConfig - .yamlParser - .parse(reader) - .left.map(e => MalformedSettings(s"Cannot parse file ${file.pathAsString.show} content. Cause: ${e.message.show}")) - .flatMap { json => - jsonConfigResolver - .resolve(json) - .left.map(e => MalformedSettings(s"Unable to resolve environment variables for file ${file.pathAsString.show}. $e.")) - } - .map(jsonWithOneLinerKeysToRegularJson) - } - } - - private def prettyCause(error: DecodingFailure) = { - error.message match { - case message if message.startsWith("DecodingFailure at") => "yaml is malformed" - case other => other - } - } -} - -final case class MalformedSettings(message: String) \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/index/BaseIndexConfigManager.scala b/core/src/main/scala/tech/beshu/ror/configuration/index/BaseIndexConfigManager.scala deleted file mode 100644 index 35d271a61c..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/index/BaseIndexConfigManager.scala +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.index - -import monix.eval.Task -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError.SpecializedError - -trait BaseIndexConfigManager[A] { - - def load(indexName: RorConfigurationIndex): Task[Either[ConfigLoaderError[IndexConfigError], A]] - - def save(config: A, rorConfigurationIndex: RorConfigurationIndex): Task[Either[SavingIndexConfigError, Unit]] - - protected final def configLoaderError(error: IndexConfigError): Task[Either[SpecializedError[IndexConfigError], A]] = - Task.now(Left(SpecializedError[IndexConfigError](error))) -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/index/Config.scala b/core/src/main/scala/tech/beshu/ror/configuration/index/Config.scala deleted file mode 100644 index 64bc97fafb..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/index/Config.scala +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.index - -private[index] object Config { - object rorSettingsIndexConst { - val id = "1" - val settingsKey = "settings" - } - - object rorTestSettingsIndexConst { - val id = "2" - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/index/IndexConfigError.scala b/core/src/main/scala/tech/beshu/ror/configuration/index/IndexConfigError.scala deleted file mode 100644 index 1a996750fa..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/index/IndexConfigError.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.index - -import cats.Show - -sealed trait IndexConfigError -object IndexConfigError { - case object IndexConfigNotExist extends IndexConfigError - case object IndexConfigUnknownStructure extends IndexConfigError - - implicit val show: Show[IndexConfigError] = Show.show { - case IndexConfigNotExist => "Cannot find settings index" - case IndexConfigUnknownStructure => s"Unknown structure of index settings" - } -} - -sealed trait SavingIndexConfigError -object SavingIndexConfigError { - case object CannotSaveConfig extends SavingIndexConfigError - - implicit val show: Show[SavingIndexConfigError] = Show.show { - case CannotSaveConfig => "Cannot save settings in index" - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/index/IndexConfigManager.scala b/core/src/main/scala/tech/beshu/ror/configuration/index/IndexConfigManager.scala deleted file mode 100644 index 7b0795a1c3..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/index/IndexConfigManager.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.index - -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} -import tech.beshu.ror.configuration.index.IndexConfigError.{IndexConfigNotExist, IndexConfigUnknownStructure} -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError.ParsingError -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.{CannotReachContentSource, CannotWriteToIndex, ContentNotFound} - -final class IndexConfigManager(indexJsonContentService: IndexJsonContentService) - (implicit environmentConfig: EnvironmentConfig) - extends BaseIndexConfigManager[RawRorConfig] - with Logging { - - override def load(indexName: RorConfigurationIndex): Task[Either[ConfigLoaderError[IndexConfigError], RawRorConfig]] = { - indexJsonContentService - .sourceOf(indexName.index, Config.rorSettingsIndexConst.id) - .flatMap { - case Right(source) => - source - .find(_._1 == Config.rorSettingsIndexConst.settingsKey) - .map { case (_, rorYamlString) => - RawRorConfig - .fromString(rorYamlString) - .map(_.left.map(ParsingError.apply)) - } - .getOrElse(configLoaderError(IndexConfigUnknownStructure)) - case Left(CannotReachContentSource) => - configLoaderError(IndexConfigNotExist) - case Left(ContentNotFound) => - configLoaderError(IndexConfigNotExist) - } - } - - override def save(config: RawRorConfig, rorConfigurationIndex: RorConfigurationIndex): Task[Either[SavingIndexConfigError, Unit]] = { - indexJsonContentService - .saveContent( - rorConfigurationIndex.index, - Config.rorSettingsIndexConst.id, - Map(Config.rorSettingsIndexConst.settingsKey -> config.raw) - ) - .map { - _.left.map { case CannotWriteToIndex => SavingIndexConfigError.CannotSaveConfig } - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/index/IndexTestConfigManager.scala b/core/src/main/scala/tech/beshu/ror/configuration/index/IndexTestConfigManager.scala deleted file mode 100644 index fe31acdb28..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/index/IndexTestConfigManager.scala +++ /dev/null @@ -1,255 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.index - -import cats.data.EitherT -import cats.implicits.* -import eu.timepit.refined.types.string.NonEmptyString -import io.circe.syntax.EncoderOps -import io.circe.{Codec, Decoder, Encoder} -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.LdapService -import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationService, ExternalAuthorizationService} -import tech.beshu.ror.accesscontrol.blocks.mocks.AuthServicesMocks -import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.ExternalAuthenticationServiceMock.ExternalAuthenticationUserMock -import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.ExternalAuthorizationServiceMock.ExternalAuthorizationServiceUserMock -import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.LdapServiceMock.LdapUserMock -import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.{ExternalAuthenticationServiceMock, ExternalAuthorizationServiceMock, LdapServiceMock} -import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId -import tech.beshu.ror.accesscontrol.domain.{Group, GroupName, RorConfigurationIndex, User} -import tech.beshu.ror.configuration.TestRorConfig.Present -import tech.beshu.ror.configuration.index.IndexConfigError.{IndexConfigNotExist, IndexConfigUnknownStructure} -import tech.beshu.ror.configuration.index.IndexTestConfigManager.Const -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError.{ParsingError, SpecializedError} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig, TestRorConfig} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.{CannotReachContentSource, CannotWriteToIndex, ContentNotFound} -import tech.beshu.ror.syntax.* -import tech.beshu.ror.utils.DurationOps.* -import tech.beshu.ror.utils.json.KeyCodec - -import java.time.format.DateTimeFormatter -import java.time.{Instant, ZoneOffset} -import scala.concurrent.duration.Duration -import scala.util.Try - -final class IndexTestConfigManager(indexJsonContentService: IndexJsonContentService) - (implicit environmentConfig: EnvironmentConfig) - extends BaseIndexConfigManager[TestRorConfig] - with Logging { - - type Error = ConfigLoaderError[IndexConfigError] - - override def load(indexName: RorConfigurationIndex): Task[Either[Error, TestRorConfig]] = { - indexJsonContentService - .sourceOf(indexName.index, Config.rorTestSettingsIndexConst.id) - .flatMap { - case Right(source) => - val properties = source.collect { case (key: String, value: String) => (key, value) } - getSettings(properties).value - case Left(CannotReachContentSource) => - configLoaderError(IndexConfigNotExist) - case Left(ContentNotFound) => - Task.now(Right(TestRorConfig.NotSet)) - } - } - - override def save(config: TestRorConfig, - rorConfigurationIndex: RorConfigurationIndex): Task[Either[SavingIndexConfigError, Unit]] = { - indexJsonContentService - .saveContent( - rorConfigurationIndex.index, - Config.rorTestSettingsIndexConst.id, - formatSettings(config) - ) - .map { - _.left.map { case CannotWriteToIndex => SavingIndexConfigError.CannotSaveConfig } - } - } - - private def getSettings(config: Map[String, String]): EitherT[Task, Error, TestRorConfig] = { - if (config.isEmpty) { - EitherT.right[Error](Task.now(TestRorConfig.NotSet)).widen[TestRorConfig] - } else { - for { - expirationTimeString <- getConfigProperty(config, Const.properties.expirationTime) - expirationTtlString <- getConfigProperty(config, Const.properties.expirationTtl) - rawRorConfigString <- getConfigProperty(config, Const.properties.settings) - authMocksConfigString <- getConfigProperty(config, Const.properties.mocks) - rawRorConfig <- EitherT { - RawRorConfig - .fromString(rawRorConfigString) - .map(_.left.map(ParsingError.apply)) - } - expirationTime <- getInstant(expirationTimeString) - expirationTtl <- getExpirationTtl(expirationTtlString) - mocks <- getMocks(authMocksConfigString) - } yield Present( - rawConfig = rawRorConfig, - mocks = mocks, - expiration = Present.ExpirationConfig(ttl = expirationTtl, validTo = expirationTime) - ) - } - } - - private def formatSettings(config: TestRorConfig): Map[String, String] = { - config match { - case TestRorConfig.NotSet => - Map.empty - case Present(rawConfig, mocks, expiration) => - Map( - Const.properties.expirationTime -> expiration.validTo.atOffset(ZoneOffset.UTC).toString, - Const.properties.expirationTtl -> expiration.ttl.value.toMillis.toString, - Const.properties.mocks -> formatMocks(mocks), - Const.properties.settings -> rawConfig.raw - ) - } - } - - private def getExpirationTtl(value: String): EitherT[Task, Error, PositiveFiniteDuration] = { - Try { - Duration - .apply(value.toLong, "ms") - .toRefinedPositive - .leftMap((_: String) => parserError) - } - .toEither - .leftMap(_ => parserError) - .flatten - .toEitherT[Task] - } - - private def parserError: Error = - SpecializedError[IndexConfigError](IndexConfigUnknownStructure) - - private def getInstant(value: String): EitherT[Task, Error, Instant] = { - Try(DateTimeFormatter.ISO_DATE_TIME.parse(value)) - .map(Instant.from) - .toEither - .toEitherT[Task] - .leftMap(_ => parserError) - } - - private def formatMocks(mocks: AuthServicesMocks): String = { - mocks.asJson.noSpaces - } - - private def getMocks(config: String): EitherT[Task, Error, AuthServicesMocks] = { - io.circe.parser.decode[AuthServicesMocks](config) - .leftMap(_ => parserError) - .toEitherT[Task] - } - - private implicit val mocksCodec: Codec[AuthServicesMocks] = { - implicit val nonEmptyStringCodec: Codec[NonEmptyString] = - Codec.from(Decoder.decodeString.emap(NonEmptyString.from), Encoder.encodeString.contramap(_.value)) - implicit val userIdCodec: Codec[User.Id] = - Codec.from(nonEmptyStringCodec.map(User.Id.apply), nonEmptyStringCodec.contramap(_.value)) - implicit val groupIdCodec: Codec[GroupId] = - Codec.from( - nonEmptyStringCodec.map(GroupId.apply), - nonEmptyStringCodec.contramap(_.value) - ) - - implicit val groupCodec: Codec[Group] = { - implicit val groupNameCodec: Codec[GroupName] = Codec.from( - nonEmptyStringCodec.map(GroupName.apply), - nonEmptyStringCodec.contramap(_.value) - ) - Codec.forProduct2("id", "name")(Group.apply)(group => (group.id, group.name)) - } - implicit val ldapServiceMock: Codec[LdapServiceMock] = { - implicit val userMock: Codec[LdapUserMock] = { - // "groups" left for backward compatibility - val encoder: Encoder[LdapUserMock] = Encoder.forProduct3("id", "groups", "userGroups")( - userMock => (userMock.id, userMock.groups.map(_.id), userMock.groups) - ) - val deprecatedFormatDecoder = Decoder.forProduct2("id", "groups")((id: User.Id, groupIds: List[GroupId]) => - LdapUserMock(id, groupIds.map(Group.from).toCovariantSet) - ) - val decoder: Decoder[LdapUserMock] = Decoder.forProduct2("id", "userGroups")(LdapUserMock.apply) - Codec.from(decoder.or(deprecatedFormatDecoder), encoder) - } - Codec.forProduct1("users")(LdapServiceMock.apply)(_.users) - } - - implicit val extAuthenticationMock: Codec[ExternalAuthenticationServiceMock] = { - implicit val userMock: Codec[ExternalAuthenticationUserMock] = - Codec.forProduct1("id")(ExternalAuthenticationUserMock.apply)(_.id) - Codec.forProduct1("users")(ExternalAuthenticationServiceMock.apply)(_.users) - } - - implicit val extAuthorizationMock: Codec[ExternalAuthorizationServiceMock] = { - implicit val userMock: Codec[ExternalAuthorizationServiceUserMock] = { - // "groups" left for backward compatibility - val encoder = Encoder.forProduct3("id", "groups", "userGroups")( - (userMock: ExternalAuthorizationServiceUserMock) => (userMock.id, userMock.groups.map(_.id), userMock.groups) - ) - val deprecatedFormatDecoder = Decoder.forProduct2("id", "groups")((id: User.Id, groupIds: List[GroupId]) => - ExternalAuthorizationServiceUserMock(id, groupIds.map(Group.from).toCovariantSet) - ) - val decoder = Decoder.forProduct2("id", "userGroups")(ExternalAuthorizationServiceUserMock.apply) - Codec.from(decoder.or(deprecatedFormatDecoder), encoder) - } - Codec.forProduct1("users")(ExternalAuthorizationServiceMock.apply)(_.users) - } - - implicit val ldapKeyCodec: KeyCodec[LdapService.Name] = KeyCodec.from[LdapService.Name]( - NonEmptyString.unapply(_).map(LdapService.Name.apply), - _.value.value - ) - - implicit val externalAuthenticationKeyCodec: KeyCodec[ExternalAuthenticationService.Name] = - KeyCodec.from[ExternalAuthenticationService.Name]( - NonEmptyString.unapply(_).map(ExternalAuthenticationService.Name.apply), - _.value.value - ) - - implicit val externalAuthorizationKeyCodec: KeyCodec[ExternalAuthorizationService.Name] = - KeyCodec.from[ExternalAuthorizationService.Name]( - NonEmptyString.unapply(_).map(ExternalAuthorizationService.Name.apply), - _.value.value - ) - - Codec.forProduct3( - "ldapMocks", - "externalAuthenticationMocks", - "externalAuthorizationMocks" - )(AuthServicesMocks.apply)(e => (e.ldapMocks, e.externalAuthenticationServiceMocks, e.externalAuthorizationServiceMocks)) - } - - private def getConfigProperty[A, B](map: Map[A, B], key: A): EitherT[Task, Error, B] = { - map - .get(key) - .toRight(parserError) - .toEitherT[Task] - } - -} - -private object IndexTestConfigManager { - object Const { - object properties { - val settings = "settings" - val expirationTtl = "expiration_ttl_millis" - val expirationTime = "expiration_timestamp" - val mocks = "auth_services_mocks" - } - } -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/ConfigLoader.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/ConfigLoader.scala deleted file mode 100644 index 50140ff067..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/ConfigLoader.scala +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader - -import cats.Show -import cats.implicits.* -import monix.eval.Task -import tech.beshu.ror.configuration.RawRorConfig -import tech.beshu.ror.configuration.RawRorConfig.ParsingRorConfigError -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError - -trait ConfigLoader[SPECIALIZED_ERROR] { - - def load(): Task[Either[ConfigLoaderError[SPECIALIZED_ERROR], RawRorConfig]] - -} - -object ConfigLoader { - - sealed trait ConfigLoaderError[+SPECIALIZED_ERROR] - object ConfigLoaderError { - final case class ParsingError(error: ParsingRorConfigError) extends ConfigLoaderError[Nothing] - final case class SpecializedError[ERROR](error: ERROR) extends ConfigLoaderError[ERROR] - - implicit def show[E: Show]: Show[ConfigLoaderError[E]] = Show.show { - case ParsingError(error) => Show[RawRorConfig.ParsingRorConfigError].show(error) - case SpecializedError(error) => Show[E].show(error) - } - } - -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/ConfigLoadingInterpreter.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/ConfigLoadingInterpreter.scala deleted file mode 100644 index a8a48bb7ca..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/ConfigLoadingInterpreter.scala +++ /dev/null @@ -1,127 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader - -import cats.data.EitherT -import cats.~> -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.configuration.ConfigLoading.LoadConfigAction -import tech.beshu.ror.configuration.EsConfig.LoadEsConfigError -import tech.beshu.ror.configuration.EsConfig.LoadEsConfigError.RorSettingsInactiveWhenXpackSecurityIsEnabled.SettingsType -import tech.beshu.ror.configuration.index.{IndexConfigError, IndexConfigManager} -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError.{ParsingError, SpecializedError} -import tech.beshu.ror.configuration.loader.FileConfigLoader.FileConfigError -import tech.beshu.ror.configuration.loader.LoadedRorConfig.* -import tech.beshu.ror.configuration.{ConfigLoading, EnvironmentConfig, EsConfig} -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.DurationOps.NonNegativeFiniteDuration - -object ConfigLoadingInterpreter extends Logging { - - def create(indexConfigManager: IndexConfigManager) - (implicit environmentConfig: EnvironmentConfig): LoadConfigAction ~> Task = new (LoadConfigAction ~> Task) { - override def apply[A](fa: LoadConfigAction[A]): Task[A] = fa match { - case ConfigLoading.LoadConfigAction.LoadEsConfig(env) => - logger.info(s"Loading Elasticsearch settings from file: ${env.elasticsearchConfig.show}") - EsConfig - .from(env) - .map(_.left.map { - case LoadEsConfigError.FileNotFound(file) => - EsFileNotExist(file.path) - case LoadEsConfigError.MalformedContent(file, msg) => - EsFileMalformed(file.path, msg) - case LoadEsConfigError.RorSettingsInactiveWhenXpackSecurityIsEnabled(aType) => - CannotUseRorConfigurationWhenXpackSecurityIsEnabled( - aType match { - case SettingsType.Ssl => "SSL configuration" - case SettingsType.Fips => "FIBS configuration" - } - ) - }) - case ConfigLoading.LoadConfigAction.ForceLoadRorConfigFromFile(path) => - logger.info(s"Loading ReadonlyREST settings forced loading from file from: ${path.show}") - EitherT(new FileConfigLoader(path).load()) - .bimap(convertFileError, ForcedFileConfig(_)) - .leftMap { error => - logger.error(s"Loading ReadonlyREST from file failed: ${error.toString}") - error - }.value - case ConfigLoading.LoadConfigAction.LoadRorConfigFromFile(path) => - logger.info(s"Loading ReadonlyREST settings from file from: ${path.show}, because index not exist") - EitherT(new FileConfigLoader(path).load()) - .bimap(convertFileError, FileConfig(_)) - .leftMap { error => - logger.error(s"Loading ReadonlyREST from file failed: ${error.toString}") - error - } - .value - case ConfigLoading.LoadConfigAction.LoadRorConfigFromIndex(configIndex, inIndexLoadingDelay) => - logger.info(s"[CLUSTERWIDE SETTINGS] Loading ReadonlyREST settings from index (${configIndex.index.show}) ...") - loadFromIndex(indexConfigManager, configIndex, inIndexLoadingDelay) - .map { rawRorConfig => - logger.debug(s"[CLUSTERWIDE SETTINGS] Loaded raw config from index: ${rawRorConfig.raw.show}") - rawRorConfig - } - .bimap(convertIndexError, IndexConfig(configIndex, _)) - .leftMap { error => - logIndexLoadingError(error) - error - }.value - } - } - - private def logIndexLoadingError[A](error: LoadedRorConfig.LoadingIndexError): Unit = { - error match { - case IndexParsingError(message) => - logger.error(s"Loading ReadonlyREST settings from index failed: ${message.show}") - case LoadedRorConfig.IndexUnknownStructure => - logger.info(s"Loading ReadonlyREST settings from index failed: index content malformed") - case LoadedRorConfig.IndexNotExist => - logger.info(s"Loading ReadonlyREST settings from index failed: cannot find index") - } - } - - private def loadFromIndex[A](indexConfigManager: IndexConfigManager, - index: RorConfigurationIndex, - loadingDelay: NonNegativeFiniteDuration) = { - EitherT { - indexConfigManager - .load(index) - .delayExecution(loadingDelay.value) - } - } - - private def convertFileError(error: ConfigLoaderError[FileConfigError]): LoadedRorConfig.Error = { - error match { - case ParsingError(error) => - val show = error.show - LoadedRorConfig.FileParsingError(show) - case SpecializedError(FileConfigError.FileNotExist(file)) => LoadedRorConfig.FileNotExist(file.path) - } - } - - private def convertIndexError(error: ConfigLoaderError[IndexConfigError])= - error match { - case ParsingError(error) => LoadedRorConfig.IndexParsingError(error.show) - case SpecializedError(IndexConfigError.IndexConfigNotExist) => LoadedRorConfig.IndexNotExist - case SpecializedError(IndexConfigError.IndexConfigUnknownStructure) => LoadedRorConfig.IndexUnknownStructure - } - -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/FileConfigLoader.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/FileConfigLoader.scala deleted file mode 100644 index f441d0d42b..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/FileConfigLoader.scala +++ /dev/null @@ -1,71 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader - -import better.files.File -import cats.Show -import cats.data.EitherT -import monix.eval.Task -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError.{ParsingError, SpecializedError} -import tech.beshu.ror.configuration.loader.FileConfigLoader.FileConfigError -import tech.beshu.ror.configuration.loader.FileConfigLoader.FileConfigError.FileNotExist -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig, RorProperties} -import tech.beshu.ror.providers.PropertiesProvider - -import java.nio.file.Path - -class FileConfigLoader(esConfigPath: Path) - (implicit environmentConfig: EnvironmentConfig) - extends ConfigLoader[FileConfigError] { - - private implicit val propertiesProvider: PropertiesProvider = environmentConfig.propertiesProvider - - def rawConfigFile: File = { - RorProperties.rorSettingsCustomFile match { - case Some(customRorFile) => customRorFile - case None => File(s"${esConfigPath.toAbsolutePath}/readonlyrest.yml") - } - } - - override def load(): Task[Either[ConfigLoaderError[FileConfigError], RawRorConfig]] = { - val file = rawConfigFile - (for { - _ <- checkIfFileExist(file) - config <- loadConfigFromFile(file) - } yield config).value - } - - private def checkIfFileExist(file: File): EitherT[Task, ConfigLoaderError[FileConfigError], File] = - EitherT.cond(file.exists, file, SpecializedError(FileNotExist(file))) - - private def loadConfigFromFile(file: File): EitherT[Task, ConfigLoaderError[FileConfigError], RawRorConfig] = { - EitherT(RawRorConfig.fromFile(file).map(_.left.map(ParsingError.apply))) - } -} - -object FileConfigLoader { - - sealed trait FileConfigError - object FileConfigError { - final case class FileNotExist(file: File) extends FileConfigError - - implicit val show: Show[FileConfigError] = Show.show { - case FileNotExist(file) => s"Cannot find settings file: ${file.pathAsString}" - } - } -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/LoadRawRorConfig.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/LoadRawRorConfig.scala deleted file mode 100644 index c2eddd57c5..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/LoadRawRorConfig.scala +++ /dev/null @@ -1,101 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader - -import cats.free.Free -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.configuration.ConfigLoading.* -import tech.beshu.ror.configuration.RawRorConfig -import tech.beshu.ror.configuration.RorProperties.{LoadingAttemptsCount, LoadingAttemptsInterval, LoadingDelay} -import tech.beshu.ror.configuration.loader.LoadedRorConfig.FileConfig -import tech.beshu.ror.utils.DurationOps.{NonNegativeFiniteDuration, RefinedDurationOps} - -import java.nio.file.Path -import scala.concurrent.duration.DurationInt -import scala.language.postfixOps - -object LoadRawRorConfig { - - type LoadResult = ErrorOr[LoadedRorConfig[RawRorConfig]] - - def loadFromIndexWithFileFallback(configurationIndex: RorConfigurationIndex, - loadingDelay: LoadingDelay, - loadingAttemptsCount: LoadingAttemptsCount, - loadingAttemptsInterval: LoadingAttemptsInterval, - fallbackConfigFilePath: Path): LoadRorConfig[LoadResult] = { - attemptLoadingConfigFromIndex( - index = configurationIndex, - currentDelay = loadingDelay.value, - attemptsCount = loadingAttemptsCount, - attemptsInterval = loadingAttemptsInterval, - fallback = loadRorConfigFromFile(fallbackConfigFilePath) - ) - } - - def loadFromFile(configFilePath: Path): LoadRorConfig[LoadResult] = { - for { - loadedConfig <- forceLoadRorConfigFromFile(configFilePath) - } yield loadedConfig - } - - def loadFromIndex(configurationIndex: RorConfigurationIndex): LoadRorConfig[LoadResult] = { - for { - result <- loadRorConfigFromIndex(configurationIndex, loadingDelay = (0 seconds).toRefinedNonNegativeUnsafe) - rawRorConfig <- result match { - case Left(LoadedRorConfig.IndexNotExist) => - Free.pure[LoadConfigAction, LoadResult](Left(LoadedRorConfig.IndexNotExist)) - case Left(LoadedRorConfig.IndexUnknownStructure) => - Free.pure[LoadConfigAction, LoadResult](Left(LoadedRorConfig.IndexUnknownStructure)) - case Left(error@LoadedRorConfig.IndexParsingError(_)) => - Free.pure[LoadConfigAction, LoadResult](Left(error)) - case Right(value) => - Free.pure[LoadConfigAction, LoadResult](Right(value)) - } - } yield rawRorConfig - } - - private def attemptLoadingConfigFromIndex(index: RorConfigurationIndex, - currentDelay: NonNegativeFiniteDuration, - attemptsCount: LoadingAttemptsCount, - attemptsInterval: LoadingAttemptsInterval, - fallback: LoadRorConfig[ErrorOr[FileConfig[RawRorConfig]]]): LoadRorConfig[LoadResult] = { - attemptsCount.value.value match { - case 0 => - fallback.map(identity) - case attemptsCount => - for { - result <- loadRorConfigFromIndex(index, loadingDelay = currentDelay) - rawRorConfig <- result match { - case Left(LoadedRorConfig.IndexNotExist) => - Free.defer(attemptLoadingConfigFromIndex( - index = index, - currentDelay = attemptsInterval.value, - attemptsCount = LoadingAttemptsCount.unsafeFrom(attemptsCount - 1), - attemptsInterval = attemptsInterval, - fallback = fallback - )) - case Left(LoadedRorConfig.IndexUnknownStructure) => - Free.pure[LoadConfigAction, LoadResult](Left(LoadedRorConfig.IndexUnknownStructure)) - case Left(error@LoadedRorConfig.IndexParsingError(_)) => - Free.pure[LoadConfigAction, LoadResult](Left(error)) - case Right(value) => - Free.pure[LoadConfigAction, LoadResult](Right(value)) - } - } yield rawRorConfig - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/LoadRawTestRorConfig.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/LoadRawTestRorConfig.scala deleted file mode 100644 index 86382c37d4..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/LoadRawTestRorConfig.scala +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader - -import cats.free.Free -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.configuration.RorProperties.{LoadingAttemptsCount, LoadingAttemptsInterval, LoadingDelay} -import tech.beshu.ror.configuration.TestConfigLoading.* -import tech.beshu.ror.configuration.TestRorConfig -import tech.beshu.ror.utils.DurationOps.NonNegativeFiniteDuration - -object LoadRawTestRorConfig { - - def loadFromIndexWithFallback(configurationIndex: RorConfigurationIndex, - loadingDelay: LoadingDelay, - indexLoadingAttemptsCount: LoadingAttemptsCount, - indexLoadingAttemptsInterval: LoadingAttemptsInterval, - fallbackConfig: TestRorConfig): LoadTestRorConfig[IndexErrorOr[LoadedTestRorConfig[TestRorConfig]]] = { - attemptLoadingConfigFromIndex( - index = configurationIndex, - currentDelay = loadingDelay.value, - attemptsCount = indexLoadingAttemptsCount, - attemptsInterval = indexLoadingAttemptsInterval, - fallback = fallbackConfig - ) - } - - private def attemptLoadingConfigFromIndex(index: RorConfigurationIndex, - currentDelay: NonNegativeFiniteDuration, - attemptsCount: LoadingAttemptsCount, - attemptsInterval: LoadingAttemptsInterval, - fallback: TestRorConfig): LoadTestRorConfig[IndexErrorOr[LoadedTestRorConfig[TestRorConfig]]] = { - attemptsCount.value.value match { - case 0 => - Free.pure[LoadTestConfigAction, IndexErrorOr[LoadedTestRorConfig[TestRorConfig]]]( - Right(LoadedTestRorConfig.FallbackConfig[TestRorConfig](fallback)) - ) - case attemptsCount => - for { - result <- loadRorConfigFromIndex(index, loadingDelay = currentDelay) - rawRorConfig <- result match { - case Left(LoadedTestRorConfig.IndexNotExist) => - Free.defer(attemptLoadingConfigFromIndex( - index = index, - currentDelay = attemptsInterval.value, - attemptsCount = LoadingAttemptsCount.unsafeFrom(attemptsCount - 1), - attemptsInterval = attemptsInterval, - fallback = fallback - )) - case Left(error@LoadedTestRorConfig.IndexUnknownStructure) => - Free.pure[LoadTestConfigAction, IndexErrorOr[LoadedTestRorConfig[TestRorConfig]]](Left(error)) - case Left(error@LoadedTestRorConfig.IndexParsingError(_)) => - Free.pure[LoadTestConfigAction, IndexErrorOr[LoadedTestRorConfig[TestRorConfig]]](Left(error)) - case Right(value) => - Free.pure[LoadTestConfigAction, IndexErrorOr[LoadedTestRorConfig[TestRorConfig]]](Right(value)) - } - } yield rawRorConfig - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/TestConfigLoadingInterpreter.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/TestConfigLoadingInterpreter.scala deleted file mode 100644 index 1325459dba..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/TestConfigLoadingInterpreter.scala +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader - -import cats.data.EitherT -import cats.~> -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex -import tech.beshu.ror.configuration.TestConfigLoading.LoadTestConfigAction -import tech.beshu.ror.configuration.index.{IndexConfigError, IndexTestConfigManager} -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError -import tech.beshu.ror.configuration.loader.ConfigLoader.ConfigLoaderError.{ParsingError, SpecializedError} -import tech.beshu.ror.configuration.loader.LoadedTestRorConfig.* -import tech.beshu.ror.configuration.{TestConfigLoading, TestRorConfig} -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.DurationOps.NonNegativeFiniteDuration - -object TestConfigLoadingInterpreter extends Logging { - - def create(indexConfigManager: IndexTestConfigManager): LoadTestConfigAction ~> Task = new (LoadTestConfigAction ~> Task) { - override def apply[A](fa: LoadTestConfigAction[A]): Task[A] = fa match { - case TestConfigLoading.LoadTestConfigAction.LoadRorConfigFromIndex(configIndex, loadingDelay) => - logger.info(s"[CLUSTERWIDE SETTINGS] Loading ReadonlyREST test settings from index (${configIndex.index.show}) ...") - loadFromIndex(indexConfigManager, configIndex, loadingDelay) - .map { testConfig => - testConfig match { - case TestRorConfig.Present(rawConfig, _, _) => - logger.debug(s"[CLUSTERWIDE SETTINGS] Loaded raw test config from index: ${rawConfig.raw.show}") - case TestRorConfig.NotSet => - logger.debug("[CLUSTERWIDE SETTINGS] There was no test settings in index. Test settings engine will be not initialized.") - } - testConfig - } - .bimap(convertIndexError, IndexConfig(configIndex, _)) - .leftMap { error => - logIndexLoadingError(error) - error - }.value - } - } - - private def logIndexLoadingError(error: LoadedTestRorConfig.LoadingIndexError): Unit = { - error match { - case IndexParsingError(message) => - logger.error(s"Loading ReadonlyREST settings from index failed: ${message.show}") - case LoadedTestRorConfig.IndexUnknownStructure => - logger.info("Loading ReadonlyREST test settings from index failed: index content malformed") - case LoadedTestRorConfig.IndexNotExist => - logger.info("Loading ReadonlyREST test settings from index failed: cannot find index") - } - } - - private def loadFromIndex(indexConfigManager: IndexTestConfigManager, - index: RorConfigurationIndex, - loadingDelay: NonNegativeFiniteDuration) = { - EitherT { - indexConfigManager - .load(index) - .delayExecution(loadingDelay.value) - } - } - - private def convertIndexError(error: ConfigLoaderError[IndexConfigError]): LoadedTestRorConfig.LoadingIndexError = - error match { - case ParsingError(error) => LoadedTestRorConfig.IndexParsingError(error.show) - case SpecializedError(IndexConfigError.IndexConfigNotExist) => LoadedTestRorConfig.IndexNotExist - case SpecializedError(IndexConfigError.IndexConfigUnknownStructure) => LoadedTestRorConfig.IndexUnknownStructure - } - -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/NodesResponse.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/NodesResponse.scala deleted file mode 100644 index d18f64c08a..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/NodesResponse.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed - -import cats.Eq -import cats.implicits.* -import io.circe.syntax.* -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.configuration.loader.external.dto.ResultDTO - -final case class NodesResponse private(resultDTO: ResultDTO) extends AnyVal - -object NodesResponse { - def create(localNode: NodeId, - responses: List[NodeResponse], - failures: List[NodeError]): NodesResponse = { - NodesResponse(ResultDTO.create(Summary.create(localNode, responses, failures))) - } - - implicit class Ops(nodesResponse: NodesResponse) { - def toJson: String = nodesResponse.resultDTO.asJson.noSpaces - } - - final case class NodeId(value: String) extends AnyVal - object NodeId { - implicit val eqNodeId: Eq[NodeId] = Eq.by(_.value) - } - final case class NodeResponse(nodeId: NodeId, loadedConfig: Either[LoadedRorConfig.Error, LoadedRorConfig[String]]) - final case class NodeError(nodeId: NodeId, cause: NodeError.Cause) - object NodeError { - sealed trait Cause - case object RorConfigActionNotFound extends Cause - case object Timeout extends Cause - final case class Unknown(detailedMessage: String) extends Cause - } -} - - - diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/RawRorConfigLoadingAction.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/RawRorConfigLoadingAction.scala deleted file mode 100644 index ddec11478a..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/RawRorConfigLoadingAction.scala +++ /dev/null @@ -1,37 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed - -import cats.data.EitherT -import monix.eval.Task -import tech.beshu.ror.configuration.index.IndexConfigManager -import tech.beshu.ror.configuration.loader.{ConfigLoadingInterpreter, LoadRawRorConfig, LoadedRorConfig} -import tech.beshu.ror.configuration.{ConfigLoading, EnvironmentConfig, RawRorConfig} -import tech.beshu.ror.es.{EsEnv, IndexJsonContentService} - -object RawRorConfigLoadingAction { - - def loadFromIndex(env: EsEnv, indexJsonContentService: IndexJsonContentService) - (implicit environmentConfig: EnvironmentConfig): Task[Either[LoadedRorConfig.Error, LoadedRorConfig[RawRorConfig]]] = { - val compiler = ConfigLoadingInterpreter.create(new IndexConfigManager(indexJsonContentService)) - (for { - esConfig <- EitherT(ConfigLoading.loadEsConfig(env)) - loadedConfig <- EitherT(LoadRawRorConfig.loadFromIndex(esConfig.rorIndex.index)) - } yield loadedConfig).value.foldMap(compiler) - } - -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/Summary.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/Summary.scala deleted file mode 100644 index 8bede481f6..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/Summary.scala +++ /dev/null @@ -1,110 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed - -import cats.implicits.* -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} - -import scala.language.postfixOps - -object Summary { - case object CurrentNodeHaveToProduceResult extends Exception - - sealed trait Error - final case class CurrentNodeConfigError(error: LoadedRorConfig.Error) extends Error - final case class CurrentNodeResponseError(detailedMessage: String) extends Error - case object CurrentNodeResponseTimeoutError extends Error - - sealed trait Warning - final case class NodeResponseTimeoutWarning(nodeId: NodeId) extends Warning - final case class NodeReturnedConfigError(nodeId: NodeId, error: LoadedRorConfig.Error) extends Warning - final case class NodeReturnedUnknownError(nodeId: NodeId, detailedMessage: String) extends Warning - final case class NodeForcedFileConfig(nodeId: NodeId) extends Warning - final case class NodeReturnedDifferentConfig(nodeId: NodeId, loadedConfig: LoadedRorConfig[String]) extends Warning - final case class Result(config: LoadedRorConfig[String], warnings: List[Warning]) - - - def create(currentNodeId: NodeId, nodesResponses: List[NodeResponse], failures: List[NodeError]): Either[Error, Result] = { - findCurrentNodeResponse(currentNodeId, nodesResponses) match { - case Some(NodeResponse(_, Right(loadedConfig))) => - val warnings = createWarnings(nodesResponses, loadedConfig, failures) - Result(loadedConfig, warnings) asRight - case Some(NodeResponse(_, Left(error))) => CurrentNodeConfigError(error) asLeft - case None => findCurrentNodeFailure(currentNodeId, failures) match { - case Some(NodeError(_, cause)) => cause match { - case NodeError.RorConfigActionNotFound => throw CurrentNodeHaveToProduceResult - case NodeError.Timeout => CurrentNodeResponseTimeoutError asLeft - case NodeError.Unknown(detailedMessage) => CurrentNodeResponseError(detailedMessage) asLeft - } - case None => throw CurrentNodeHaveToProduceResult - } - } - } - - private def createWarnings(nodesResponses: List[NodeResponse], - loadedConfig: LoadedRorConfig[String], - failures: List[NodeError]): List[Warning] = { - createNodeErrorWarnings(nodesResponses) ++ - createNodeNodeForcedFileConfigWarnings(nodesResponses) ++ - createNodeReturnedDifferentConfigWarnings(loadedConfig, nodesResponses) ++ - createNodeReturnedUnknownError(failures) ++ - createNodeResponseTimeoutWarnings(failures) ++ - Nil - } - - private def findCurrentNodeFailure(currentNodeId: NodeId, nodesResponses: List[NodeError]) = - nodesResponses.find(_.nodeId === currentNodeId) - - private def findCurrentNodeResponse(currentNodeId: NodeId, nodesResponses: List[NodeResponse]) = - nodesResponses.find(_.nodeId === currentNodeId) - - private def createNodeReturnedUnknownError(failures: List[NodeError]): List[NodeReturnedUnknownError] = - failures.flatMap { - case NodeError(nodeId, NodeError.Unknown(detailedMessage)) => NodeReturnedUnknownError(nodeId, detailedMessage) :: Nil - case _ => Nil - } - - private def createNodeErrorWarnings(otherResponses: List[NodeResponse]): List[NodeReturnedConfigError] = - otherResponses.flatMap { - case NodeResponse(nodeId, Left(error)) => NodeReturnedConfigError(nodeId, error) :: Nil - case _ => Nil - } - - private def createNodeResponseTimeoutWarnings(failures: List[NodeError]): List[NodeResponseTimeoutWarning] = - failures.flatMap { - case NodeError(nodeId, NodeError.Timeout) => NodeResponseTimeoutWarning(nodeId) :: Nil - case _ => Nil - } - - private def createNodeNodeForcedFileConfigWarnings(otherResponses: List[NodeResponse]): List[NodeForcedFileConfig] = - otherResponses.flatMap { - case NodeResponse(nodeId, Right(LoadedRorConfig.ForcedFileConfig(_))) => NodeForcedFileConfig(nodeId) :: Nil - case _ => Nil - } - - private def createNodeReturnedDifferentConfigWarnings(currentNodeConfig: LoadedRorConfig[String], - otherResponses: List[NodeResponse]): List[NodeReturnedDifferentConfig] = { - otherResponses.foldLeft(List.empty[NodeReturnedDifferentConfig]) { - case (warnings, NodeResponse(_, Right(`currentNodeConfig`))) => warnings - case (warnings, NodeResponse(nodeId, Right(loadedConfig))) => NodeReturnedDifferentConfig(nodeId, loadedConfig) :: warnings - case (warnings, _) => warnings - } - } - -} - diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/domain.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/domain.scala deleted file mode 100644 index 6165c29012..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/domain.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed - -import tech.beshu.ror.configuration.loader.LoadedRorConfig - -import scala.concurrent.duration.* -import scala.language.postfixOps - -final case class NodeConfig(loadedConfig: Either[LoadedRorConfig.Error, LoadedRorConfig[String]]) -final case class Timeout(nanos: Long) extends AnyVal -final case class NodeConfigRequest(timeout: Timeout) -object NodeConfigRequest { - val defaultTimeout: Timeout = Timeout(nanos = (10 seconds).toNanos) -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/NodeConfigRequestSerializer.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/NodeConfigRequestSerializer.scala deleted file mode 100644 index 5531e56209..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/NodeConfigRequestSerializer.scala +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed.internode - -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest -import tech.beshu.ror.configuration.loader.distributed.internode.dto.NodeConfigRequestDTO - -object NodeConfigRequestSerializer { - - import io.circe.parser - import io.circe.syntax.* - - def serialize(nodeConfigRequest: NodeConfigRequest): String = { - NodeConfigRequestDTO.create(nodeConfigRequest).asJson.noSpaces - } - - def parse(str: String): NodeConfigRequest = { - parser.decode[NodeConfigRequestDTO](str).map(NodeConfigRequestDTO.fromDto).toTry.get - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/NodeConfigSerializer.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/NodeConfigSerializer.scala deleted file mode 100644 index dfc6714c20..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/NodeConfigSerializer.scala +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed.internode - -import tech.beshu.ror.configuration.loader.distributed.NodeConfig -import tech.beshu.ror.configuration.loader.distributed.internode.dto.NodeConfigDTO - -object NodeConfigSerializer { - - import io.circe.parser - import io.circe.syntax.* - - def serialize(nodeConfig: NodeConfig): String = { - NodeConfigDTO.create(nodeConfig).asJson.noSpaces - } - - def parse(str: String): NodeConfig = { - parser.decode[NodeConfigDTO](str).map(NodeConfigDTO.fromDto).toTry.get - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/LoadedConfigDTO.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/LoadedConfigDTO.scala deleted file mode 100644 index 3e5e8925a7..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/LoadedConfigDTO.scala +++ /dev/null @@ -1,104 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed.internode.dto - -import eu.timepit.refined.types.string.NonEmptyString -import io.circe.* -import io.circe.syntax.EncoderOps -import tech.beshu.ror.accesscontrol.domain.{IndexName, RorConfigurationIndex} -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.utils.CirceOps.* - -import scala.collection.immutable.Map - -sealed trait LoadedConfigDTO - -object LoadedConfigDTO { - - implicit val codec: Codec[LoadedConfigDTO] = codecWithTypeDiscriminator( - encode = { - case dto: IndexConfigDTO => - derivedEncoderWithType[IndexConfigDTO]("IndexConfigDTO")(dto) - case dto: FileConfigDTO => - derivedEncoderWithType[FileConfigDTO]("FileConfigDTO")(dto) - case dto: ForcedFileConfigDTO => - derivedEncoderWithType[ForcedFileConfigDTO]("ForcedFileConfigDTO")(dto) - }, - decoders = Map( - "IndexConfigDTO" -> derivedDecoderOfSubtype[LoadedConfigDTO, IndexConfigDTO], - "FileConfigDTO" -> derivedDecoderOfSubtype[LoadedConfigDTO, FileConfigDTO], - "ForcedFileConfigDTO" -> derivedDecoderOfSubtype[LoadedConfigDTO, ForcedFileConfigDTO], - ) - ) - - def create(o: LoadedRorConfig[String]): LoadedConfigDTO = o match { - case o: LoadedRorConfig.FileConfig[String] => FileConfigDTO.create(o) - case o: LoadedRorConfig.ForcedFileConfig[String] => ForcedFileConfigDTO.create(o) - case o: LoadedRorConfig.IndexConfig[String] => IndexConfigDTO.create(o) - } - - def fromDto(o: LoadedConfigDTO): LoadedRorConfig[String] = o match { - case o: IndexConfigDTO => IndexConfigDTO.fromDto(o) - case o: FileConfigDTO => FileConfigDTO.fromDto(o) - case o: ForcedFileConfigDTO => ForcedFileConfigDTO.fromDto(o) - } - - final case class IndexConfigDTO(indexName: String, value: String) extends LoadedConfigDTO - object IndexConfigDTO { - def create(o: LoadedRorConfig.IndexConfig[String]): IndexConfigDTO = - new IndexConfigDTO( - indexName = o.indexName.index.name.value, - value = o.value, - ) - - def fromDto(o: IndexConfigDTO): LoadedRorConfig.IndexConfig[String] = LoadedRorConfig.IndexConfig( - indexName = RorConfigurationIndex(IndexName.Full(NonEmptyString.unsafeFrom(o.indexName))), - value = o.value, - ) - implicit class Ops(o: IndexConfigDTO) { - implicit def fromDto: LoadedRorConfig.IndexConfig[String] = IndexConfigDTO.fromDto(o) - } - } - final case class FileConfigDTO(value: String) extends LoadedConfigDTO - object FileConfigDTO { - def create(o: LoadedRorConfig.FileConfig[String]): FileConfigDTO = - new FileConfigDTO( - value = o.value, - ) - - def fromDto(o: FileConfigDTO): LoadedRorConfig.FileConfig[String] = LoadedRorConfig.FileConfig( - value = o.value, - ) - implicit class Ops(o: FileConfigDTO) { - implicit def fromDto: LoadedRorConfig.FileConfig[String] = FileConfigDTO.fromDto(o) - } - } - final case class ForcedFileConfigDTO(value: String) extends LoadedConfigDTO - object ForcedFileConfigDTO { - def create(o: LoadedRorConfig.ForcedFileConfig[String]): ForcedFileConfigDTO = - new ForcedFileConfigDTO( - value = o.value, - ) - - def fromDto(o: ForcedFileConfigDTO): LoadedRorConfig.ForcedFileConfig[String] = LoadedRorConfig.ForcedFileConfig( - value = o.value, - ) - implicit class Ops(o: ForcedFileConfigDTO) { - implicit def fromDto: LoadedRorConfig.ForcedFileConfig[String] = ForcedFileConfigDTO.fromDto(o) - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/LoadedConfigErrorDto.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/LoadedConfigErrorDto.scala deleted file mode 100644 index 90aa27dd29..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/LoadedConfigErrorDto.scala +++ /dev/null @@ -1,194 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed.internode.dto - -import io.circe.{Codec, Decoder, Encoder} -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.utils.CirceOps.* - -import java.nio.file.Paths - -sealed trait LoadedConfigErrorDto - -object LoadedConfigErrorDto { - - implicit val codec: Codec[LoadedConfigErrorDto] = codecWithTypeDiscriminator( - encode = { - case dto: FileParsingErrorDTO => - derivedEncoderWithType[FileParsingErrorDTO]("FileParsingErrorDTO")(dto) - case dto: FileNotExistDTO => - derivedEncoderWithType[FileNotExistDTO]("FileNotExistDTO")(dto) - case dto: EsFileNotExistDTO => - derivedEncoderWithType[EsFileNotExistDTO]("EsFileNotExistDTO")(dto) - case dto: EsFileMalformedDTO => - derivedEncoderWithType[EsFileMalformedDTO]("EsFileMalformedDTO")(dto) - case dto: CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO => - derivedEncoderWithType[CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO]("CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO")(dto) - case dto: IndexParsingErrorDTO => - derivedEncoderWithType[IndexParsingErrorDTO]("FileParsingErrorDTO")(dto) - case IndexUnknownStructureDTO => - derivedEncoderWithType[IndexUnknownStructureDTO.type]("IndexUnknownStructureDTO")(IndexUnknownStructureDTO) - case IndexNotExistDTO => - derivedEncoderWithType[IndexNotExistDTO.type]("IndexNotExistDTO")(IndexNotExistDTO) - }, - decoders = Map( - "FileParsingErrorDTO" -> derivedDecoderOfSubtype[LoadedConfigErrorDto, FileParsingErrorDTO], - "FileNotExistDTO" -> derivedDecoderOfSubtype[LoadedConfigErrorDto, FileNotExistDTO], - "EsFileNotExistDTO" -> derivedDecoderOfSubtype[LoadedConfigErrorDto, EsFileNotExistDTO], - "EsFileMalformedDTO" -> derivedDecoderOfSubtype[LoadedConfigErrorDto, EsFileMalformedDTO], - "CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO" -> derivedDecoderOfSubtype[LoadedConfigErrorDto, CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO], - "FileParsingErrorDTO" -> derivedDecoderOfSubtype[LoadedConfigErrorDto, FileParsingErrorDTO], - "IndexUnknownStructureDTO" -> derivedDecoderOfSubtype[LoadedConfigErrorDto, IndexUnknownStructureDTO.type], - "IndexNotExistDTO" -> derivedDecoderOfSubtype[LoadedConfigErrorDto, IndexNotExistDTO.type], - ) - ) - - def create(error: LoadedRorConfig.Error): LoadedConfigErrorDto = error match { - case o: LoadedRorConfig.FileParsingError => FileParsingErrorDTO.create(o) - case o: LoadedRorConfig.FileNotExist => FileNotExistDTO.create(o) - case o: LoadedRorConfig.EsFileNotExist => EsFileNotExistDTO.create(o) - case o: LoadedRorConfig.EsFileMalformed => EsFileMalformedDTO.create(o) - case o: LoadedRorConfig.CannotUseRorConfigurationWhenXpackSecurityIsEnabled => - CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO.create(o) - case o: LoadedRorConfig.IndexParsingError => IndexParsingErrorDTO.create(o) - case _: LoadedRorConfig.IndexUnknownStructure.type => IndexUnknownStructureDTO - case _: LoadedRorConfig.IndexNotExist.type => IndexNotExistDTO - } - - def fromDto(o: LoadedConfigErrorDto): LoadedRorConfig.Error = o match { - case o: FileParsingErrorDTO => FileParsingErrorDTO.fromDto(o) - case o: FileNotExistDTO => FileNotExistDTO.fromDto(o) - case o: EsFileNotExistDTO => EsFileNotExistDTO.fromDto(o) - case o: EsFileMalformedDTO => EsFileMalformedDTO.fromDto(o) - case o: CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO => - CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO.fromDto(o) - case o: IndexParsingErrorDTO => IndexParsingErrorDTO.fromDto(o) - case _: IndexUnknownStructureDTO.type => LoadedRorConfig.IndexUnknownStructure - case _: IndexNotExistDTO.type => LoadedRorConfig.IndexNotExist - } - - final case class FileParsingErrorDTO(message: String) extends LoadedConfigErrorDto - object FileParsingErrorDTO { - def create(o: LoadedRorConfig.FileParsingError): FileParsingErrorDTO = - new FileParsingErrorDTO( - message = o.message, - ) - - def fromDto(o: FileParsingErrorDTO): LoadedRorConfig.FileParsingError = LoadedRorConfig.FileParsingError( - message = o.message, - ) - implicit class Ops(o: FileParsingErrorDTO) { - implicit def fromDto: LoadedRorConfig.FileParsingError = FileParsingErrorDTO.fromDto(o) - } - } - - final case class FileNotExistDTO(path: String) extends LoadedConfigErrorDto - object FileNotExistDTO { - def create(o: LoadedRorConfig.FileNotExist): FileNotExistDTO = - new FileNotExistDTO( - path = o.path.toString, - ) - - def fromDto(o: FileNotExistDTO): LoadedRorConfig.FileNotExist = LoadedRorConfig.FileNotExist( - path = Paths.get(o.path), - ) - implicit class Ops(o: FileNotExistDTO) { - implicit def fromDto: LoadedRorConfig.FileNotExist = FileNotExistDTO.fromDto(o) - } - } - - final case class EsFileNotExistDTO(path: String) extends LoadedConfigErrorDto - object EsFileNotExistDTO { - def create(o: LoadedRorConfig.EsFileNotExist): EsFileNotExistDTO = - new EsFileNotExistDTO( - path = o.path.toString, - ) - - def fromDto(o: EsFileNotExistDTO): LoadedRorConfig.EsFileNotExist = LoadedRorConfig.EsFileNotExist( - path = Paths.get(o.path), - ) - implicit class Ops(o: EsFileNotExistDTO) { - implicit def fromDto: LoadedRorConfig.EsFileNotExist = EsFileNotExistDTO.fromDto(o) - } - } - - final case class EsFileMalformedDTO(path: String, message: String) extends LoadedConfigErrorDto - object EsFileMalformedDTO { - def create(o: LoadedRorConfig.EsFileMalformed): EsFileMalformedDTO = - new EsFileMalformedDTO( - path = o.path.toString, - message = o.message, - ) - - def fromDto(o: EsFileMalformedDTO): LoadedRorConfig.EsFileMalformed = LoadedRorConfig.EsFileMalformed( - path = Paths.get(o.path), - message = o.message, - ) - implicit class Ops(o: EsFileMalformedDTO) { - implicit def fromDto: LoadedRorConfig.EsFileMalformed = EsFileMalformedDTO.fromDto(o) - } - } - - final case class CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO(typeOfConfiguration: String) extends LoadedConfigErrorDto - object CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO { - def create(o: LoadedRorConfig.CannotUseRorConfigurationWhenXpackSecurityIsEnabled): CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO = - new CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO( - typeOfConfiguration = o.typeOfConfiguration - ) - - def fromDto(o: CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO): LoadedRorConfig.CannotUseRorConfigurationWhenXpackSecurityIsEnabled = - LoadedRorConfig.CannotUseRorConfigurationWhenXpackSecurityIsEnabled( - typeOfConfiguration = o.typeOfConfiguration - ) - implicit class Ops(o: CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO) { - implicit def fromDto: LoadedRorConfig.CannotUseRorConfigurationWhenXpackSecurityIsEnabled = - CannotUseRorConfigurationWhenXpackSecurityIsEnabledDTO.fromDto(o) - } - } - - final case class IndexParsingErrorDTO(message: String) extends LoadedConfigErrorDto - object IndexParsingErrorDTO { - def create(o: LoadedRorConfig.IndexParsingError): IndexParsingErrorDTO = - new IndexParsingErrorDTO( - message = o.message, - ) - - def fromDto(o: IndexParsingErrorDTO): LoadedRorConfig.IndexParsingError = LoadedRorConfig.IndexParsingError( - message = o.message, - ) - implicit class Ops(o: IndexParsingErrorDTO) { - implicit def fromDto: LoadedRorConfig.IndexParsingError = IndexParsingErrorDTO.fromDto(o) - } - } - - case object IndexUnknownStructureDTO extends LoadedConfigErrorDto { - implicit lazy val codecIndexUnknownStructureDto: Codec[IndexUnknownStructureDTO.type] = { - val enc = Encoder.encodeString.contramap[IndexUnknownStructureDTO.type](_ => "unknown_structure") - val dec = Decoder.decodeString.map(_ => IndexUnknownStructureDTO) - Codec.from(dec, enc) - } - } - - case object IndexNotExistDTO extends LoadedConfigErrorDto { - implicit lazy val codecIndexNotFoundDto: Codec[IndexNotExistDTO.type] = { - val enc = Encoder.encodeString.contramap[IndexNotExistDTO.type](_ => "index_not_exist") - val dec = Decoder.decodeString.map(_ => IndexNotExistDTO) - Codec.from(dec, enc) - } - } - -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/NodeConfigDTO.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/NodeConfigDTO.scala deleted file mode 100644 index 09c220cb95..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/NodeConfigDTO.scala +++ /dev/null @@ -1,42 +0,0 @@ -package tech.beshu.ror.configuration.loader.distributed.internode.dto - -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ - -import cats.implicits.* -import io.circe.Codec -import io.circe.generic.semiauto.deriveCodec -import tech.beshu.ror.configuration.loader.distributed.NodeConfig - -final case class NodeConfigDTO(loadedConfig: Either[LoadedConfigErrorDto, LoadedConfigDTO]) - -object NodeConfigDTO { - - implicit val codec: Codec[NodeConfigDTO] = deriveCodec - - def create(o: NodeConfig): NodeConfigDTO = - new NodeConfigDTO( - loadedConfig = o.loadedConfig.bimap(LoadedConfigErrorDto.create, LoadedConfigDTO.create), - ) - - def fromDto(o: NodeConfigDTO): NodeConfig = NodeConfig( - loadedConfig = o.loadedConfig.bimap(LoadedConfigErrorDto.fromDto, LoadedConfigDTO.fromDto), - ) - implicit class Ops(o: NodeConfigDTO) { - implicit def fromDto: NodeConfig = NodeConfigDTO.fromDto(o) - } -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/NodeConfigRequestDTO.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/NodeConfigRequestDTO.scala deleted file mode 100644 index db56d8912d..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/NodeConfigRequestDTO.scala +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed.internode.dto - -import io.circe.Codec -import io.circe.generic.semiauto.deriveCodec -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} - -final case class NodeConfigRequestDTO(nanos: Long) - -object NodeConfigRequestDTO { - implicit val codec: Codec[NodeConfigRequestDTO] = deriveCodec - - def create(o: NodeConfigRequest): NodeConfigRequestDTO = - new NodeConfigRequestDTO( - nanos = o.timeout.nanos, - ) - - def fromDto(o: NodeConfigRequestDTO): NodeConfigRequest = NodeConfigRequest( - timeout = Timeout(o.nanos), - ) - implicit class Ops(o: NodeConfigRequestDTO) { - implicit def fromDto: NodeConfigRequest = NodeConfigRequestDTO.fromDto(o) - } -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/package.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/package.scala deleted file mode 100644 index 4b85c22c62..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/distributed/internode/dto/package.scala +++ /dev/null @@ -1,23 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.distributed.internode - -import io.circe.{Codec, Decoder, Encoder} - -package object dto { - implicit def codecEither[A: Decoder : Encoder, B: Decoder : Encoder]: Codec[Either[A, B]] = io.circe.generic.semiauto.deriveCodec -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/domain.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/domain.scala deleted file mode 100644 index 637ea1d874..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/domain.scala +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader - -import tech.beshu.ror.accesscontrol.domain.RorConfigurationIndex - -import java.nio.file.Path - -sealed trait LoadedRorConfig[A] -object LoadedRorConfig { - final case class FileConfig[A](value: A) extends LoadedRorConfig[A] - final case class ForcedFileConfig[A](value: A) extends LoadedRorConfig[A] - final case class IndexConfig[A](indexName: RorConfigurationIndex, value: A) extends LoadedRorConfig[A] - - sealed trait Error - final case class FileParsingError(message: String) extends LoadedRorConfig.Error - final case class FileNotExist(path: Path) extends LoadedRorConfig.Error - final case class EsFileNotExist(path: Path) extends LoadedRorConfig.Error - final case class EsFileMalformed(path: Path, message: String) extends LoadedRorConfig.Error - final case class CannotUseRorConfigurationWhenXpackSecurityIsEnabled(typeOfConfiguration: String) extends LoadedRorConfig.Error - - sealed trait LoadingIndexError - final case class IndexParsingError(message: String) extends LoadedRorConfig.Error with LoadingIndexError - case object IndexUnknownStructure extends LoadedRorConfig.Error with LoadingIndexError - case object IndexNotExist extends LoadedRorConfig.Error with LoadingIndexError -} - -sealed trait LoadedTestRorConfig[A] -object LoadedTestRorConfig { - final case class IndexConfig[A](indexName: RorConfigurationIndex, value: A) extends LoadedTestRorConfig[A] - final case class FallbackConfig[A](value: A) extends LoadedTestRorConfig[A] - - sealed trait LoadingIndexError - final case class IndexParsingError(message: String) extends LoadingIndexError - case object IndexUnknownStructure extends LoadingIndexError - case object IndexNotExist extends LoadingIndexError -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/LoadedConfigDTO.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/LoadedConfigDTO.scala deleted file mode 100644 index 9a2c41ebed..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/LoadedConfigDTO.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.external.dto - -import io.circe.Codec -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.utils.CirceOps.* - -sealed trait LoadedConfigDTO { - def raw: String -} - -object LoadedConfigDTO { - implicit val codec: Codec[LoadedConfigDTO] = codecWithTypeDiscriminator( - encode = { - case dto: FILE_CONFIG => - derivedEncoderWithType[FILE_CONFIG]("FILE_CONFIG")(dto) - case dto: FORCED_FILE_CONFIG => - derivedEncoderWithType[FORCED_FILE_CONFIG]("FORCED_FILE_CONFIG")(dto) - case dto: INDEX_CONFIG => - derivedEncoderWithType[INDEX_CONFIG]("INDEX_CONFIG")(dto) - }, - decoders = Map( - "FILE_CONFIG" -> derivedDecoderOfSubtype[LoadedConfigDTO, FILE_CONFIG], - "FORCED_FILE_CONFIG" -> derivedDecoderOfSubtype[LoadedConfigDTO, FORCED_FILE_CONFIG], - "INDEX_CONFIG" -> derivedDecoderOfSubtype[LoadedConfigDTO, INDEX_CONFIG], - ) - ) - def create(o: LoadedRorConfig[String]): LoadedConfigDTO = o match { - case LoadedRorConfig.FileConfig(value) => FILE_CONFIG(value) - case LoadedRorConfig.ForcedFileConfig(value) => FORCED_FILE_CONFIG(value) - case LoadedRorConfig.IndexConfig(indexName, value) => INDEX_CONFIG(indexName.index.name.value, value) - } - final case class FILE_CONFIG(raw: String) extends LoadedConfigDTO - final case class FORCED_FILE_CONFIG(raw: String) extends LoadedConfigDTO - final case class INDEX_CONFIG(indexName: String, raw: String) extends LoadedConfigDTO -} - diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/NodesResponseWaringDTO.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/NodesResponseWaringDTO.scala deleted file mode 100644 index 59970b82a1..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/NodesResponseWaringDTO.scala +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.external.dto - -import cats.implicits.* -import io.circe.Codec -import tech.beshu.ror.configuration.loader.distributed.Summary -import tech.beshu.ror.configuration.loader.distributed.Summary.{NodeForcedFileConfig, NodeReturnedDifferentConfig} -import tech.beshu.ror.utils.CirceOps.* - -sealed trait NodesResponseWaringDTO - -object NodesResponseWaringDTO { - - implicit val codec: Codec[NodesResponseWaringDTO] = codecWithTypeDiscriminator( - encode = { - case dto: NODE_RETURNED_DIFFERENT_CONFIG => - derivedEncoderWithType[NODE_RETURNED_DIFFERENT_CONFIG]("NODE_RETURNED_DIFFERENT_CONFIG")(dto) - case dto: NODE_FORCED_FILE_CONFIG => - derivedEncoderWithType[NODE_FORCED_FILE_CONFIG]("NODE_FORCED_FILE_CONFIG")(dto) - case dto: NODE_RETURNED_CONFIG_ERROR => - derivedEncoderWithType[NODE_RETURNED_CONFIG_ERROR]("NODE_RETURNED_CONFIG_ERROR")(dto) - case dto: NODE_RETURNED_UNKNOWN_ERROR => - derivedEncoderWithType[NODE_RETURNED_UNKNOWN_ERROR]("NODE_RETURNED_UNKNOWN_ERROR")(dto) - case dto: NODE_RESPONSE_TIMEOUT_ERROR => - derivedEncoderWithType[NODE_RESPONSE_TIMEOUT_ERROR]("NODE_RESPONSE_TIMEOUT_ERROR")(dto) - }, - decoders = Map( - "NODE_RETURNED_DIFFERENT_CONFIG" -> derivedDecoderOfSubtype[NodesResponseWaringDTO, NODE_RETURNED_DIFFERENT_CONFIG], - "NODE_FORCED_FILE_CONFIG" -> derivedDecoderOfSubtype[NodesResponseWaringDTO, NODE_FORCED_FILE_CONFIG], - "NODE_RETURNED_CONFIG_ERROR" -> derivedDecoderOfSubtype[NodesResponseWaringDTO, NODE_RETURNED_CONFIG_ERROR], - "NODE_RETURNED_UNKNOWN_ERROR" -> derivedDecoderOfSubtype[NodesResponseWaringDTO, NODE_RETURNED_UNKNOWN_ERROR], - "NODE_RESPONSE_TIMEOUT_ERROR" -> derivedDecoderOfSubtype[NodesResponseWaringDTO, NODE_RESPONSE_TIMEOUT_ERROR], - ) - ) - - def create(warning: Summary.Warning): NodesResponseWaringDTO = warning match { - case w: Summary.NodeReturnedConfigError => NODE_RETURNED_CONFIG_ERROR.create(w) - case w: Summary.NodeReturnedUnknownError => NODE_RETURNED_UNKNOWN_ERROR.create(w) - case w: Summary.NodeForcedFileConfig => NODE_FORCED_FILE_CONFIG.create(w) - case w: Summary.NodeReturnedDifferentConfig => NODE_RETURNED_DIFFERENT_CONFIG.create(w) - case w: Summary.NodeResponseTimeoutWarning => NODE_RESPONSE_TIMEOUT_ERROR.create(w) - } - final case class NODE_RETURNED_DIFFERENT_CONFIG(nodeId: String, loadedConfig: LoadedConfigDTO) extends NodesResponseWaringDTO - object NODE_RETURNED_DIFFERENT_CONFIG { - def create(o: NodeReturnedDifferentConfig): NODE_RETURNED_DIFFERENT_CONFIG = - new NODE_RETURNED_DIFFERENT_CONFIG( - nodeId = o.nodeId.value, - loadedConfig = LoadedConfigDTO.create(o.loadedConfig), - ) - } - final case class NODE_FORCED_FILE_CONFIG(nodeId: String) extends NodesResponseWaringDTO - object NODE_FORCED_FILE_CONFIG { - def create(o: NodeForcedFileConfig): NODE_FORCED_FILE_CONFIG = - new NODE_FORCED_FILE_CONFIG( - nodeId = o.nodeId.value, - ) - } - final case class NODE_RETURNED_CONFIG_ERROR(nodeId: String, error: String) extends NodesResponseWaringDTO - object NODE_RETURNED_CONFIG_ERROR { - def create(o: Summary.NodeReturnedConfigError): NODE_RETURNED_CONFIG_ERROR = - new NODE_RETURNED_CONFIG_ERROR( - nodeId = o.nodeId.value, - error = o.error.show, - ) - } - final case class NODE_RETURNED_UNKNOWN_ERROR(nodeId: String, detailedMessage: String) extends NodesResponseWaringDTO - object NODE_RETURNED_UNKNOWN_ERROR { - def create(o: Summary.NodeReturnedUnknownError): NODE_RETURNED_UNKNOWN_ERROR = - new NODE_RETURNED_UNKNOWN_ERROR( - nodeId = o.nodeId.value, - detailedMessage = o.detailedMessage, - ) - } - final case class NODE_RESPONSE_TIMEOUT_ERROR(nodeId: String) extends NodesResponseWaringDTO - object NODE_RESPONSE_TIMEOUT_ERROR { - def create(o: Summary.NodeResponseTimeoutWarning): NODE_RESPONSE_TIMEOUT_ERROR = - new NODE_RESPONSE_TIMEOUT_ERROR( - nodeId = o.nodeId.value, - ) - } -} - - - diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/ResultDTO.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/ResultDTO.scala deleted file mode 100644 index ec70b27224..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/ResultDTO.scala +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.external.dto - -import cats.implicits.* -import io.circe.Codec -import io.circe.generic.semiauto.deriveCodec -import tech.beshu.ror.configuration.loader.distributed.Summary -import tech.beshu.ror.configuration.loader.distributed.Summary.{Error, Result} - -final case class ResultDTO(config: Option[LoadedConfigDTO], - warnings: List[NodesResponseWaringDTO], - error: Option[String]) - -object ResultDTO { - def create(o: Either[Error, Result]): ResultDTO = - o.bimap(createError, createResult).merge - - private def createResult(result: Result) = - ResultDTO(LoadedConfigDTO.create(result.config).some, result.warnings.map(NodesResponseWaringDTO.create), None) - - private def createError(error: Error) = { - val message = error match { - case Summary.CurrentNodeResponseError(detailedMessage) => s"current node response error: ${detailedMessage.show}" - case Summary.CurrentNodeConfigError(error) => show"current node returned error: ${error.show}" - case Summary.CurrentNodeResponseTimeoutError => "current node response timeout" - } - ResultDTO(None, Nil, message.some) - } - - implicit val codec: Codec[ResultDTO] = deriveCodec -} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/package.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/package.scala deleted file mode 100644 index acf68a3df0..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/external/dto/package.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration.loader.external - -import cats.Show -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.implicits.* - -package object dto { - implicit val showLoadedConfigError: Show[LoadedRorConfig.Error] = Show.show { - case LoadedRorConfig.FileNotExist(path) => s"""file not exist: ${path.show}""" - case LoadedRorConfig.FileParsingError(message) => s"""file parsing error: ${message.show}""" - case LoadedRorConfig.EsFileNotExist(path) => s"""ES file not exist: ${path.show}""" - case LoadedRorConfig.EsFileMalformed(path, message) => s"""ES file malformed: ${path.show} ${message.show}""" - case LoadedRorConfig.CannotUseRorConfigurationWhenXpackSecurityIsEnabled(typeOfConfiguration) => - s"""ROR ${typeOfConfiguration.show} cannot be used when XPack Security is enabled""" - case LoadedRorConfig.IndexParsingError(message) => s"""index parsing error: ${message.show}""" - case LoadedRorConfig.IndexUnknownStructure => "index unknown structure" - case LoadedRorConfig.IndexNotExist => "index not exist" - } -} diff --git a/core/src/main/scala/tech/beshu/ror/configuration/loader/package.scala b/core/src/main/scala/tech/beshu/ror/configuration/loader/package.scala deleted file mode 100644 index fb55f6224d..0000000000 --- a/core/src/main/scala/tech/beshu/ror/configuration/loader/package.scala +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.configuration - -import cats.Functor -import tech.beshu.ror.configuration.loader.LoadedRorConfig.{FileConfig, ForcedFileConfig, IndexConfig} - -package object loader { - - implicit class LoadedConfigOps[A](fa: LoadedRorConfig[A]) { - lazy val value: A = fa match { - case FileConfig(value) => value - case ForcedFileConfig(value) => value - case IndexConfig(_, value) => value - } - } - - implicit class LoadedTestConfigOps[A](fa: LoadedTestRorConfig[A]) { - lazy val value: A = fa match { - case LoadedTestRorConfig.IndexConfig(_, value) => value - case LoadedTestRorConfig.FallbackConfig(value) => value - } - } - - implicit val functorLoadedConfig: Functor[LoadedRorConfig] = new Functor[LoadedRorConfig] { - override def map[A, B](fa: LoadedRorConfig[A])(f: A => B): LoadedRorConfig[B] = fa match { - case FileConfig(value) => FileConfig(f(value)) - case ForcedFileConfig(value) => ForcedFileConfig(f(value)) - case IndexConfig(indexName, value) => IndexConfig(indexName, f(value)) - } - } -} diff --git a/core/src/main/scala/tech/beshu/ror/constants.scala b/core/src/main/scala/tech/beshu/ror/constants.scala index 4f5f2c29e4..e1bc67bf8c 100644 --- a/core/src/main/scala/tech/beshu/ror/constants.scala +++ b/core/src/main/scala/tech/beshu/ror/constants.scala @@ -33,22 +33,22 @@ object constants { val CURRENT_USER_METADATA_PATH = "/_readonlyrest/metadata/current_user/" val AUDIT_EVENT_COLLECTOR_PATH = "/_readonlyrest/admin/audit/event/" - val FORCE_RELOAD_CONFIG_PATH = "/_readonlyrest/admin/refreshconfig/" - val UPDATE_INDEX_CONFIG_PATH = "/_readonlyrest/admin/config/" - val PROVIDE_TEST_CONFIG_PATH = "/_readonlyrest/admin/config/test/" - val UPDATE_TEST_CONFIG_PATH = "/_readonlyrest/admin/config/test/" - val DELETE_TEST_CONFIG_PATH = "/_readonlyrest/admin/config/test/" + val FORCE_RELOAD_SETTINGS_PATH = "/_readonlyrest/admin/refreshconfig/" + val UPDATE_INDEX_SETTINGS_PATH = "/_readonlyrest/admin/config/" + val PROVIDE_TEST_SETTINGS_PATH = "/_readonlyrest/admin/config/test/" + val UPDATE_TEST_SETTINGS_PATH = "/_readonlyrest/admin/config/test/" + val DELETE_TEST_SETTINGS_PATH = "/_readonlyrest/admin/config/test/" val PROVIDE_LOCAL_USERS_PATH = "/_readonlyrest/admin/config/test/localusers/" val CONFIGURE_AUTH_MOCK_PATH = "/_readonlyrest/admin/config/test/authmock/" val PROVIDE_AUTH_MOCK_PATH = "/_readonlyrest/admin/config/test/authmock/" - val PROVIDE_INDEX_CONFIG_PATH = "/_readonlyrest/admin/config/" - val PROVIDE_FILE_CONFIG_PATH = "/_readonlyrest/admin/config/file/" - val MANAGE_ROR_CONFIG_PATH = "/_readonlyrest/admin/config/load" + val PROVIDE_INDEX_SETTINGS_PATH = "/_readonlyrest/admin/config/" + val PROVIDE_FILE_SETTINGS_PATH = "/_readonlyrest/admin/config/file/" val FIELDS_TRANSIENT = "_fields" - val FIELDS_ALWAYS_ALLOW: MutableSet[String] = - MutableSet("_id", "_uid", "_type", "_version", "_seq_no", "_primary_term", "_parent", "_routing", "_timestamp", "_ttl", "_size", "_index") + val FIELDS_ALWAYS_ALLOW: MutableSet[String] = MutableSet( + "_id", "_uid", "_type", "_version", "_seq_no", "_primary_term", "_parent", "_routing", "_timestamp", "_ttl", "_size", "_index" + ) val AUDIT_LOG_DEFAULT_INDEX_TEMPLATE = "'readonlyrest_audit-'yyyy-MM-dd" diff --git a/core/src/main/scala/tech/beshu/ror/es/EsEnv.scala b/core/src/main/scala/tech/beshu/ror/es/EsEnv.scala index 1f1729f90a..ebeae78055 100644 --- a/core/src/main/scala/tech/beshu/ror/es/EsEnv.scala +++ b/core/src/main/scala/tech/beshu/ror/es/EsEnv.scala @@ -16,23 +16,23 @@ */ package tech.beshu.ror.es -import better.files.File +import better.files.* +import tech.beshu.ror.accesscontrol.domain.EsConfigFile -import java.nio.file.Path import scala.util.Try -final case class EsEnv(configPath: Path, modulesPath: Path, esVersion: EsVersion, esNodeSettings: EsNodeSettings) { +final case class EsEnv(configDir: File, + modulesDir: File, + esVersion: EsVersion, + esNodeSettings: EsNodeSettings) { def isOssDistribution: Boolean = { Try { - !modulesPath.resolve("x-pack-security").toFile.exists() + !(modulesDir / "x-pack-security").exists } getOrElse { false } } - def elasticsearchConfig: File = { - File(s"${configPath.toAbsolutePath}/elasticsearch.yml") - } - + def elasticsearchConfig: EsConfigFile = EsConfigFile.default(this) } diff --git a/core/src/main/scala/tech/beshu/ror/es/IndexDocumentManager.scala b/core/src/main/scala/tech/beshu/ror/es/IndexDocumentManager.scala new file mode 100644 index 0000000000..abcf9d99cf --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/es/IndexDocumentManager.scala @@ -0,0 +1,38 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es + +import io.circe.Json +import monix.eval.Task +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.es.IndexDocumentManager.{ReadError, WriteError} + +trait IndexDocumentManager { + def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] + def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] +} + +object IndexDocumentManager { + + sealed trait ReadError + case object IndexNotFound extends ReadError + case object DocumentNotFound extends ReadError + case object DocumentUnreachable extends ReadError + + sealed trait WriteError + case object CannotWriteToIndex extends WriteError +} diff --git a/core/src/main/scala/tech/beshu/ror/es/IndexJsonContentService.scala b/core/src/main/scala/tech/beshu/ror/es/IndexJsonContentService.scala deleted file mode 100644 index 77dcdcaec7..0000000000 --- a/core/src/main/scala/tech/beshu/ror/es/IndexJsonContentService.scala +++ /dev/null @@ -1,37 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es - -import monix.eval.Task -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.es.IndexJsonContentService.{ReadError, WriteError} - -trait IndexJsonContentService { - - def sourceOf(index: IndexName.Full, id: String): Task[Either[ReadError, Map[String, String]]] - def saveContent(index: IndexName.Full, id: String, content: Map[String, String]): Task[Either[WriteError, Unit]] -} - -object IndexJsonContentService { - - sealed trait ReadError - case object ContentNotFound extends ReadError - case object CannotReachContentSource extends ReadError - - sealed trait WriteError - case object CannotWriteToIndex extends WriteError -} diff --git a/core/src/main/scala/tech/beshu/ror/implicits.scala b/core/src/main/scala/tech/beshu/ror/implicits.scala index d106f83606..3c206c5b45 100644 --- a/core/src/main/scala/tech/beshu/ror/implicits.scala +++ b/core/src/main/scala/tech/beshu/ror/implicits.scala @@ -57,8 +57,18 @@ import tech.beshu.ror.accesscontrol.factory.BlockValidator.BlockValidationError import tech.beshu.ror.accesscontrol.factory.BlockValidator.BlockValidationError.{KibanaRuleTogetherWith, KibanaUserDataRuleTogetherWith} import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient import tech.beshu.ror.accesscontrol.request.RequestContext +import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.providers.EnvVarProvider.EnvVarName import tech.beshu.ror.providers.PropertiesProvider.PropName +import tech.beshu.ror.settings.es.RorCoreSettingsLoadingStrategy.CoreRefreshSettings +import tech.beshu.ror.settings.es.RorCoreSettingsLoadingStrategy.LoadingRetryStrategySettings.{LoadingAttemptsCount, LoadingAttemptsInterval, LoadingDelay} +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader +import tech.beshu.ror.settings.ror.RawRorSettingsYamlParser.ParsingRorSettingsError +import tech.beshu.ror.settings.ror.RawRorSettingsYamlParser.ParsingRorSettingsError.{InvalidContent, MoreThanOneRorSection, NoRorSection} +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource.SettingsLoadingError +import tech.beshu.ror.settings.ror.source.ReadWriteSettingsSource.SettingsSavingError +import tech.beshu.ror.settings.ror.source.{FileSettingsSource, IndexSettingsSource} +import tech.beshu.ror.settings.ror.{MainRorSettings, TestRorSettings} import tech.beshu.ror.utils.ScalaOps.* import tech.beshu.ror.utils.json.JsonPath import tech.beshu.ror.utils.set.CovariantSet @@ -401,4 +411,58 @@ trait LogsShowInstances case MustBePresent(value) => value.show case MustBeAbsent(value) => s"~${value.show}" } + + implicit val coreRefreshSettingsShow: Show[CoreRefreshSettings] = Show.show { + case CoreRefreshSettings.Disabled => "0 sec" + case CoreRefreshSettings.Enabled(interval) => interval.value.toString() + } + + implicit val esConfigFileShow: Show[EsConfigFile] = Show.show(_.file.show) + + implicit val loadingDelayShow: Show[LoadingDelay] = Show[FiniteDuration].contramap(_.value.value) + + implicit val loadingAttemptsCountShow: Show[LoadingAttemptsCount] = Show[Int].contramap(_.value.value) + + implicit val loadingAttemptsIntervalShow: Show[LoadingAttemptsInterval] = Show[FiniteDuration].contramap(_.value.value) + + implicit val testRorSettingsShow: Show[TestRorSettings] = Show.show(_.rawSettings.rawYaml) + + implicit val mainRorSettingsShow: Show[MainRorSettings] = Show.show(_.rawSettings.rawYaml) + + implicit val esConfigBasedRorSettingsLoadingErrorShow: Show[YamlFileBasedSettingsLoader.LoadingError] = Show.show { + case YamlFileBasedSettingsLoader.LoadingError.FileNotFound(file) => + s"Cannot find settings file: [${file.show}]" + case YamlFileBasedSettingsLoader.LoadingError.MalformedSettings(file, message) => + s"Settings file is malformed: [${file.show}], ${message.show}" + } + + implicit val parsingRorSettingsErrorShow: Show[ParsingRorSettingsError] = Show.show { + case NoRorSection => "Cannot find any 'readonlyrest' section in settings" + case MoreThanOneRorSection => "Only one 'readonlyrest' section is required" + case InvalidContent(ex) => s"Settings content is malformed. Details: ${ex.getMessage.show}" + } + + implicit val indexSettingsSourceLoadingErrorShow: Show[IndexSettingsSource.LoadingError] = Show.show { + case IndexSettingsSource.LoadingError.IndexNotFound => "Cannot find ReadonlyREST settings index" + case IndexSettingsSource.LoadingError.DocumentNotFound => "Cannot found document with ReadonlyREST settings" + } + + implicit val indexSettingsSourceSavingErrorShow: Show[IndexSettingsSource.SavingError] = Show.show { + case IndexSettingsSource.SavingError.CannotSaveSettings => "Cannot save settings in the ReadonlyREST index" + } + + implicit val fileSettingsSourceLoadingErrorShow: Show[FileSettingsSource.LoadingError] = Show.show { + case FileSettingsSource.LoadingError.FileNotExist(file) => s"Cannot find settings file: ${file.pathAsString}" + } + + implicit val startingFailureShow: Show[StartingFailure] = Show.show(_.message) + + implicit def settingsLoadingErrorShow[ERROR: Show]: Show[SettingsLoadingError[ERROR]] = Show.show { + case SettingsLoadingError.SettingsMalformed(cause) => s"ROR settings are malformed: $cause" + case SettingsLoadingError.SourceSpecificError(error) => implicitly[Show[ERROR]].show(error) + } + + implicit def settingsSavingErrorShow[ERROR: Show]: Show[SettingsSavingError[ERROR]] = Show.show { + case SettingsSavingError.SourceSpecificError(error) => implicitly[Show[ERROR]].show(error) + } } diff --git a/core/src/main/scala/tech/beshu/ror/settings/RorProperties.scala b/core/src/main/scala/tech/beshu/ror/settings/RorProperties.scala new file mode 100644 index 0000000000..910e74ded1 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/RorProperties.scala @@ -0,0 +1,164 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings + +import better.files.File +import cats.Show +import eu.timepit.refined.api.Refined +import eu.timepit.refined.numeric.NonNegative +import eu.timepit.refined.types.string.NonEmptyString +import org.apache.logging.log4j.scala.Logging +import squants.information.{Information, Megabytes} +import tech.beshu.ror.accesscontrol.domain.RorSettingsFile +import tech.beshu.ror.implicits.* +import tech.beshu.ror.providers.PropertiesProvider +import tech.beshu.ror.providers.PropertiesProvider.PropName +import tech.beshu.ror.settings.es.RorCoreSettingsLoadingStrategy.CoreRefreshSettings +import tech.beshu.ror.settings.es.RorCoreSettingsLoadingStrategy.LoadingRetryStrategySettings.{LoadingAttemptsCount, LoadingAttemptsInterval, LoadingDelay} +import tech.beshu.ror.utils.DurationOps.* +import tech.beshu.ror.utils.RefinedUtils.* + +import java.util.concurrent.TimeUnit +import scala.concurrent.duration.* +import scala.language.postfixOps +import scala.util.{Failure, Success, Try} + +object RorProperties extends Logging { + + private object defaults { + val refreshInterval: PositiveFiniteDuration = (5 second).toRefinedPositiveUnsafe + val loadingDelay: NonNegativeFiniteDuration = (5 second).toRefinedNonNegativeUnsafe + val loadingAttemptsCount: Int Refined NonNegative = Refined.unsafeApply(5) + val loadingAttemptsInterval: NonNegativeFiniteDuration = (5 second).toRefinedNonNegativeUnsafe + val rorSettingsMaxSize: Information = Megabytes(3) + } + + private object keys { + val rorSettingsFilePath: NonEmptyString = nes("com.readonlyrest.settings.file.path") + val rorSettingsRefreshInterval: NonEmptyString = nes("com.readonlyrest.settings.refresh.interval") + val startupIndexLoadingDelay: NonEmptyString = nes("com.readonlyrest.settings.loading.delay") + val startupIndexLoadingAttemptsInterval: NonEmptyString = nes("com.readonlyrest.settings.loading.attempts.interval") + val startupIndexLoadingAttemptsCount: NonEmptyString = nes("com.readonlyrest.settings.loading.attempts.count") + val rorSettingsMaxSize: NonEmptyString = nes("com.readonlyrest.settings.maxSize") + } + + def rorSettingsCustomFile(implicit propertiesProvider: PropertiesProvider): Option[RorSettingsFile] = + propertiesProvider + .getProperty(PropName(keys.rorSettingsFilePath)) + .map(f => RorSettingsFile(File(f))) + + def rorCoreRefreshSettings(implicit propertiesProvider: PropertiesProvider): CoreRefreshSettings = + getProperty( + keys.rorSettingsRefreshInterval, + str => toCoreRefreshSettings(str), + CoreRefreshSettings.Enabled(defaults.refreshInterval) + ) + + def atStartupRorIndexSettingsLoadingAttemptsInterval(implicit propertiesProvider: PropertiesProvider): LoadingAttemptsInterval = + getProperty( + keys.startupIndexLoadingAttemptsInterval, + str => toLoadingAttemptsInterval(str), + LoadingAttemptsInterval(defaults.loadingAttemptsInterval) + ) + + def atStartupRorIndexSettingsLoadingAttemptsCount(implicit propertiesProvider: PropertiesProvider): LoadingAttemptsCount = + getProperty( + keys.startupIndexLoadingAttemptsCount, + str => toLoadingAttempts(str), + LoadingAttemptsCount(defaults.loadingAttemptsCount) + ) + + def atStartupRorIndexSettingLoadingDelay(implicit propertiesProvider: PropertiesProvider): LoadingDelay = + getProperty( + keys.startupIndexLoadingDelay, + str => toLoadingDelay(str), + LoadingDelay(defaults.loadingDelay) + ) + + def rorSettingsMaxSize(implicit propertiesProvider: PropertiesProvider): Information = + getProperty( + keys.rorSettingsMaxSize, + Information.parseString, + defaults.rorSettingsMaxSize + ) + + private def getProperty[T: Show](name: NonEmptyString, fromString: String => Try[T], default: T) + (implicit provider: PropertiesProvider) = { + getPropertyOf( + name, + fromString, + { + logger.info(s"No '${name.show}' property found. Using default: ${default.show}") + default + } + ) + } + + private def getPropertyOf[T](name: NonEmptyString, fromString: String => Try[T], default: => T) + (implicit provider: PropertiesProvider): T = { + provider + .getProperty(PropName(name)) + .map { stringValue => + fromString(stringValue) match { + case Success(value) => value + case Failure(ex) => throw new IllegalArgumentException(s"Invalid format of parameter ${name.show}=${stringValue.show}", ex) + } + } + .getOrElse { + default + } + } + + private def toCoreRefreshSettings(value: String): Try[CoreRefreshSettings] = toPositiveFiniteDuration(value).map { + case Some(value) => CoreRefreshSettings.Enabled(value) + case None => CoreRefreshSettings.Disabled + } + + private def toLoadingAttemptsInterval(value: String): Try[LoadingAttemptsInterval] = + toNonNegativeFiniteDuration(value).map(LoadingAttemptsInterval.apply) + + private def toLoadingDelay(value: String): Try[LoadingDelay] = + toNonNegativeFiniteDuration(value).map(LoadingDelay.apply) + + private def toLoadingAttempts(value: String): Try[LoadingAttemptsCount] = + toNonNegativeInt(value).map(LoadingAttemptsCount.apply) + + private def toPositiveFiniteDuration(value: String): Try[Option[PositiveFiniteDuration]] = Try { + durationFrom(value) match { + case d if d == Duration.Zero => None + case d => Some(d.toRefinedPositiveUnsafe) + } + } + + private def toNonNegativeFiniteDuration(value: String): Try[NonNegativeFiniteDuration] = Try { + durationFrom(value).toRefinedNonNegativeUnsafe + } + + private def durationFrom(value: String) = { + Try(value.toLong) match { + case Success(seconds) => FiniteDuration(seconds, TimeUnit.SECONDS) + case Failure(_) => Duration(value) + } + } + + private def toNonNegativeInt(value: String): Try[Int Refined NonNegative] = Try { + Try(Integer.valueOf(value)) match { + case Success(int) if int >= 0 => Refined.unsafeApply(int) + case Success(_) | Failure(_) => throw new IllegalArgumentException(s"Cannot convert '${value.show}' to non-negative integer") + } + } +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/es/EsConfigBasedRorSettings.scala b/core/src/main/scala/tech/beshu/ror/settings/es/EsConfigBasedRorSettings.scala new file mode 100644 index 0000000000..895b640e53 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/es/EsConfigBasedRorSettings.scala @@ -0,0 +1,44 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.es + +import cats.data.EitherT +import monix.eval.Task +import tech.beshu.ror.SystemContext +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError + +import scala.language.{implicitConversions, postfixOps} + +final case class EsConfigBasedRorSettings(settingsSource: RorSettingsSourcesConfig, + boot: RorBootSettings, + ssl: Option[RorSslSettings], + rorCoreSettingsLoadingStrategy: RorCoreSettingsLoadingStrategy) + +object EsConfigBasedRorSettings { + + def from(esEnv: EsEnv) + (implicit systemContext: SystemContext): Task[Either[LoadingError, EsConfigBasedRorSettings]] = { + val result = for { + settingsSource <- EitherT(RorSettingsSourcesConfig.from(esEnv)) + bootSettings <- EitherT(RorBootSettings.load(esEnv)) + sslSettings <- EitherT(RorSslSettings.load(esEnv, settingsSource.settingsFile)) + loadingRorCoreStrategy <- EitherT(RorCoreSettingsLoadingStrategy.load(esEnv)) + } yield EsConfigBasedRorSettings(settingsSource, bootSettings, sslSettings, loadingRorCoreStrategy) + result.value + } +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/es/RorBootSettings.scala b/core/src/main/scala/tech/beshu/ror/settings/es/RorBootSettings.scala new file mode 100644 index 0000000000..7f3f02adc4 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/es/RorBootSettings.scala @@ -0,0 +1,110 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.es + +import cats.data.NonEmptyList +import io.circe.Decoder +import monix.eval.Task +import tech.beshu.ror.SystemContext +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError +import tech.beshu.ror.utils.yaml.YamlKeyDecoder + +final case class RorBootSettings(rorNotStartedResponse: RorNotStartedResponse, + rorFailedToStartResponse: RorFailedToStartResponse) + +object RorBootSettings extends YamlFileBasedSettingsLoaderSupport { + + def load(env: EsEnv) + (implicit systemContext: SystemContext): Task[Either[LoadingError, RorBootSettings]] = { + implicit val rorBootSettingsDecoder: Decoder[RorBootSettings] = decoders.rorBootSettingsDecoder + loadSetting[RorBootSettings](env, "ROR boot settings") + } + + final case class RorNotStartedResponse(httpCode: RorNotStartedResponse.HttpCode) + object RorNotStartedResponse { + sealed trait HttpCode + object HttpCode { + case object `403` extends HttpCode + case object `503` extends HttpCode + } + } + + final case class RorFailedToStartResponse(httpCode: RorFailedToStartResponse.HttpCode) + object RorFailedToStartResponse { + sealed trait HttpCode + object HttpCode { + case object `403` extends HttpCode + case object `503` extends HttpCode + } + } + + private object decoders { + + object consts { + val rorSection = "readonlyrest" + val rorNotStartedResponseCode = "not_started_response_code" + val rorFailedTpStartResponseCode = "failed_to_start_response_code" + } + + def rorBootSettingsDecoder: Decoder[RorBootSettings] = Decoder.instance { c => + for { + notStarted <- c.as[RorNotStartedResponse] + failedToStart <- c.as[RorFailedToStartResponse] + } yield RorBootSettings(notStarted, failedToStart) + } + + private implicit val rorNotStartedResponseDecoder: Decoder[RorNotStartedResponse] = { + val segments = NonEmptyList.of(consts.rorSection, consts.rorNotStartedResponseCode) + + implicit val httpCodeDecoder: Decoder[RorNotStartedResponse.HttpCode] = Decoder.decodeInt.emap { + case 403 => Right(RorNotStartedResponse.HttpCode.`403`) + case 503 => Right(RorNotStartedResponse.HttpCode.`503`) + case other => Left( + s"Unsupported response code [${other.show}] for ${segments.toList.mkString(".").show}. Supported response codes are: 403, 503." + ) + } + + YamlKeyDecoder[RorNotStartedResponse.HttpCode]( + path = segments, + default = RorNotStartedResponse.HttpCode.`403` + ) + .map(RorNotStartedResponse.apply) + } + + private implicit val rorFailedToStartResponseDecoder: Decoder[RorFailedToStartResponse] = { + val segments = NonEmptyList.of(consts.rorSection, consts.rorFailedTpStartResponseCode) + + implicit val httpCodeDecoder: Decoder[RorFailedToStartResponse.HttpCode] = Decoder.decodeInt.emap { + case 403 => Right(RorFailedToStartResponse.HttpCode.`403`) + case 503 => Right(RorFailedToStartResponse.HttpCode.`503`) + case other => Left( + s"Unsupported response code [${other.show}] for ${segments.toList.mkString(".").show}. Supported response codes are: 403, 503." + ) + } + + YamlKeyDecoder[RorFailedToStartResponse.HttpCode]( + path = segments, + default = RorFailedToStartResponse.HttpCode.`403` + ) + .map(RorFailedToStartResponse.apply) + } + } + +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/es/RorCoreSettingsLoadingStrategy.scala b/core/src/main/scala/tech/beshu/ror/settings/es/RorCoreSettingsLoadingStrategy.scala new file mode 100644 index 0000000000..28dc3e8e75 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/es/RorCoreSettingsLoadingStrategy.scala @@ -0,0 +1,117 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.es + +import cats.data.NonEmptyList +import eu.timepit.refined.api.Refined +import eu.timepit.refined.numeric.NonNegative +import io.circe.Decoder +import monix.eval.Task +import tech.beshu.ror.SystemContext +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.providers.PropertiesProvider +import tech.beshu.ror.settings.RorProperties +import tech.beshu.ror.settings.es.RorCoreSettingsLoadingStrategy.LoadingRetryStrategySettings.{LoadingAttemptsCount, LoadingAttemptsInterval, LoadingDelay} +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError +import tech.beshu.ror.utils.DurationOps.{NonNegativeFiniteDuration, PositiveFiniteDuration, RefinedDurationOps} +import tech.beshu.ror.utils.yaml.YamlKeyDecoder + +import scala.concurrent.duration.{DurationInt, FiniteDuration} +import scala.language.{implicitConversions, postfixOps} + +sealed trait RorCoreSettingsLoadingStrategy +object RorCoreSettingsLoadingStrategy extends YamlFileBasedSettingsLoaderSupport { + + case object ForceLoadingFromFileSettings extends RorCoreSettingsLoadingStrategy + final case class LoadFromIndexWithFileFallback(indexLoadingRetrySettings: LoadingRetryStrategySettings, + coreRefreshSettings: CoreRefreshSettings) + extends RorCoreSettingsLoadingStrategy + + final case class LoadingRetryStrategySettings(attemptsInterval: LoadingAttemptsInterval, + attemptsCount: LoadingAttemptsCount, + delay: LoadingDelay) + object LoadingRetryStrategySettings { + + final case class LoadingAttemptsCount(value: Int Refined NonNegative) extends AnyVal + object LoadingAttemptsCount { + def unsafeFrom(value: Int): LoadingAttemptsCount = LoadingAttemptsCount(Refined.unsafeApply(value)) + + val zero: LoadingAttemptsCount = LoadingAttemptsCount.unsafeFrom(0) + } + + final case class LoadingAttemptsInterval(value: NonNegativeFiniteDuration) extends AnyVal + object LoadingAttemptsInterval { + def unsafeFrom(value: FiniteDuration): LoadingAttemptsInterval = LoadingAttemptsInterval(value.toRefinedNonNegativeUnsafe) + } + + final case class LoadingDelay(value: NonNegativeFiniteDuration) extends AnyVal + object LoadingDelay { + val none: LoadingDelay = unsafeFrom(0 seconds) + + def unsafeFrom(value: FiniteDuration): LoadingDelay = LoadingDelay(value.toRefinedNonNegativeUnsafe) + } + } + + sealed trait CoreRefreshSettings + object CoreRefreshSettings { + case object Disabled extends CoreRefreshSettings + final case class Enabled(refreshInterval: PositiveFiniteDuration) extends CoreRefreshSettings + } + + def load(esEnv: EsEnv) + (implicit systemContext: SystemContext): Task[Either[LoadingError, RorCoreSettingsLoadingStrategy]] = { + implicit val decoder: Decoder[RorCoreSettingsLoadingStrategy] = decoders.loadRorCoreStrategyDecoder(esEnv) + loadSetting[RorCoreSettingsLoadingStrategy](esEnv, "ROR loading core strategy settings") + } + + private object decoders { + implicit def loadRorCoreStrategyDecoder(esEnv: EsEnv) + (implicit systemContext: SystemContext): Decoder[RorCoreSettingsLoadingStrategy] = { + YamlKeyDecoder[Boolean]( + path = NonEmptyList.of("readonlyrest", "force_load_from_file"), + default = false + ) flatMap { + case true => + Decoder.const(RorCoreSettingsLoadingStrategy.ForceLoadingFromFileSettings) + case false => + for { + loadingRetryStrategySettings <- loadLoadingRetryStrategySettings(systemContext.propertiesProvider) + coreRefreshIntervalSettings <- loadCoreRefreshSettings(systemContext.propertiesProvider) + } yield RorCoreSettingsLoadingStrategy.LoadFromIndexWithFileFallback( + loadingRetryStrategySettings, coreRefreshIntervalSettings + ) + } + } + + private def loadCoreRefreshSettings(propertiesProvider: PropertiesProvider): Decoder[CoreRefreshSettings] = { + Decoder.instance(_ => Right(RorProperties.rorCoreRefreshSettings(propertiesProvider))) + } + + private def loadLoadingRetryStrategySettings(propertiesProvider: PropertiesProvider): Decoder[LoadingRetryStrategySettings] = { + for { + loadingAttemptsInterval <- Decoder.instance(_ => Right(RorProperties.atStartupRorIndexSettingsLoadingAttemptsInterval(propertiesProvider))) + loadingAttemptsCount <- Decoder.instance(_ => Right(RorProperties.atStartupRorIndexSettingsLoadingAttemptsCount(propertiesProvider))) + loadingDelay <- Decoder.instance(_ => Right(RorProperties.atStartupRorIndexSettingLoadingDelay(propertiesProvider))) + } yield LoadingRetryStrategySettings( + loadingAttemptsInterval, + loadingAttemptsCount, + loadingDelay + ) + } + } + +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/es/RorSettingsSourcesConfig.scala b/core/src/main/scala/tech/beshu/ror/settings/es/RorSettingsSourcesConfig.scala new file mode 100644 index 0000000000..dfa602ad82 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/es/RorSettingsSourcesConfig.scala @@ -0,0 +1,82 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.es + +import cats.data.NonEmptyList +import eu.timepit.refined.types.string.NonEmptyString +import io.circe.Decoder +import monix.eval.Task +import squants.information.Information +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.domain.{IndexName, RorSettingsFile, RorSettingsIndex} +import tech.beshu.ror.accesscontrol.factory.decoders.common.* +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.settings.RorProperties +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError +import tech.beshu.ror.utils.yaml.YamlKeyDecoder + +final case class RorSettingsSourcesConfig(settingsIndex: RorSettingsIndex, + settingsFile: RorSettingsFile, + settingsMaxSize: Information) + +object RorSettingsSourcesConfig extends YamlFileBasedSettingsLoaderSupport { + + def from(esEnv: EsEnv) + (implicit systemContext: SystemContext): Task[Either[LoadingError, RorSettingsSourcesConfig]] = { + implicit val decoder: Decoder[RorSettingsSourcesConfig] = for { + rorSettingsIndex <- decoders.rorSettingsIndexDecoder + rorSettingsFile <- decoders.rorSettingsFileDecoder(esEnv) + rorSettingsMaxSize <- decoders.settingsMaxSizeDecoder() + } yield RorSettingsSourcesConfig( + rorSettingsIndex, + rorSettingsFile, + rorSettingsMaxSize + ) + loadSetting[RorSettingsSourcesConfig](esEnv, "ROR settings source settings") + } + + private object decoders { + + val rorSettingsIndexDecoder: Decoder[RorSettingsIndex] = { + implicit val indexNameDecoder: Decoder[RorSettingsIndex] = + Decoder[NonEmptyString] + .map(IndexName.Full.apply) + .map(RorSettingsIndex.apply) + YamlKeyDecoder[RorSettingsIndex]( + path = NonEmptyList.of("readonlyrest", "settings_index"), + default = RorSettingsIndex.default + ) + } + + def rorSettingsFileDecoder(esEnv: EsEnv) + (implicit systemContext: SystemContext): Decoder[RorSettingsFile] = + Decoder.instance(_ => Right( + RorProperties + .rorSettingsCustomFile(systemContext.propertiesProvider) + .getOrElse(RorSettingsFile.default(esEnv)) + )) + + def settingsMaxSizeDecoder() + (implicit systemContext: SystemContext): Decoder[Information] = { + Decoder.instance(_ => Right( + RorProperties.rorSettingsMaxSize(systemContext.propertiesProvider) + )) + } + + } + +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/settings/es/RorSslSettings.scala b/core/src/main/scala/tech/beshu/ror/settings/es/RorSslSettings.scala new file mode 100644 index 0000000000..958646a025 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/es/RorSslSettings.scala @@ -0,0 +1,437 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.es + +import better.files.* +import cats.data.{EitherT, NonEmptyList} +import io.circe.{Decoder, DecodingFailure, HCursor} +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.domain.{EsConfigFile, RorSettingsFile} +import tech.beshu.ror.accesscontrol.utils.CirceOps.DecoderHelpers +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.SslSettings.* +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError +import tech.beshu.ror.utils.SSLCertHelper +import tech.beshu.ror.utils.yaml.YamlKeyDecoder + +sealed trait RorSslSettings +object RorSslSettings extends YamlFileBasedSettingsLoaderSupport with Logging { + + final case class OnlyExternalSslSettings(ssl: ExternalSslSettings) extends RorSslSettings + final case class OnlyInternodeSslSettings(ssl: InternodeSslSettings) extends RorSslSettings + final case class ExternalAndInternodeSslSettings(external: ExternalSslSettings, + internode: InternodeSslSettings) + extends RorSslSettings + + implicit class ExtractSsl(val rorSsl: RorSslSettings) extends AnyVal { + def externalSsl: Option[ExternalSslSettings] = rorSsl match { + case OnlyExternalSslSettings(ssl) => Some(ssl) + case OnlyInternodeSslSettings(_) => None + case ExternalAndInternodeSslSettings(ssl, _) => Some(ssl) + } + + def internodeSsl: Option[InternodeSslSettings] = rorSsl match { + case OnlyExternalSslSettings(_) => None + case OnlyInternodeSslSettings(ssl) => Some(ssl) + case ExternalAndInternodeSslSettings(_, ssl) => Some(ssl) + } + } + + implicit class IsSslFipsCompliant(val fipsMode: FipsMode) extends AnyVal { + def isSslFipsCompliant: Boolean = fipsMode match { + case FipsMode.NonFips => false + case FipsMode.SslOnly => true + } + } + + def load(esEnv: EsEnv, + rorSettingsFile: RorSettingsFile) + (implicit systemContext: SystemContext): Task[Either[LoadingError, Option[RorSslSettings]]] = { + val result = for { + xpackSecuritySettings <- loadXpackSecuritySettings(esEnv) + rorSslSettings <- loadRorSslSetting(esEnv.elasticsearchConfig, rorSettingsFile, xpackSecuritySettings) + } yield rorSslSettings + result.value + } + + private def loadXpackSecuritySettings(esEnv: EsEnv) + (implicit systemContext: SystemContext): EitherT[Task, LoadingError, XpackSecuritySettings] = { + EitherT { + implicit val decoder: Decoder[XpackSecuritySettings] = xpackSettingsDecoder(esEnv.isOssDistribution) + loadSetting[XpackSecuritySettings](esEnv, "X-Pack settings") + } + } + + private def loadRorSslSetting(esConfigFile: EsConfigFile, + rorSettingsFile: RorSettingsFile, + xpackSecuritySettings: XpackSecuritySettings) + (implicit systemContext: SystemContext): EitherT[Task, LoadingError, Option[RorSslSettings]] = { + implicit val rorSslSettingsDecoder: Decoder[Option[RorSslSettings]] = SslDecoders.rorSslDecoder(esConfigFile.file.parent) + loadSslSettingsFrom(esConfigFile.file) + .flatMap { + case None => + fallbackToRorSettingsFile(rorSettingsFile) + case Some(ssl) => + EitherT.rightT(Some(ssl)) + } + .subflatMap { + case Some(ssl) if xpackSecuritySettings.enabled => + Left(LoadingError.MalformedSettings(esConfigFile.file, "Cannot use ROR SSL when XPack Security is enabled"): LoadingError) + case rorSsl@(Some(_) | None) => + Right(rorSsl) + } + } + + private def fallbackToRorSettingsFile(rorSettingsFile: RorSettingsFile) + (implicit decoder: Decoder[Option[RorSslSettings]], + systemContext: SystemContext): EitherT[Task, LoadingError, Option[RorSslSettings]] = { + val settingsFile = rorSettingsFile.file + if (settingsFile.exists) { + for { + settings <- loadSslSettingsFrom(settingsFile) + _ <- lift(settings match { + case None => logger.warn(s"Defining SSL settings in ReadonlyREST file is deprecated and will be removed in the future. Move your ReadonlyREST SSL settings to Elasticsearch config file. See https://docs.readonlyrest.com/elasticsearch#encryption for details") + case Some(_) => + }) + } yield settings + } else { + EitherT.rightT(None) + } + } + + private def loadSslSettingsFrom(settingsFile: File) + (implicit decoder: Decoder[Option[RorSslSettings]], + systemContext: SystemContext) = { + for { + _ <- lift(logger.info(s"Trying to load ROR SSL settings from '${settingsFile.show}' file ...")) + settings <- EitherT(loadSetting[Option[RorSslSettings]](settingsFile, "ROR SSL settings")) + _ <- lift(logger.info(settings match { + case Some(_) => s"ROR SSL settings loaded from '${settingsFile.show}' file." + case None => s"No ROR SSL settings found in '${settingsFile.show}' file." + })) + } yield settings + } + + private final case class XpackSecuritySettings(enabled: Boolean) + + private def xpackSettingsDecoder(isOssDistribution: Boolean): Decoder[XpackSecuritySettings] = { + if (isOssDistribution) { + Decoder.const(XpackSecuritySettings(enabled = false)) + } else { + val booleanDecoder = YamlKeyDecoder[Boolean]( + path = NonEmptyList.of("xpack", "security", "enabled"), + default = true + ) + val stringDecoder = YamlKeyDecoder[String]( + path = NonEmptyList.of("xpack", "security", "enabled"), + default = "true" + ) map { + _.toBoolean + } + (booleanDecoder or stringDecoder) map XpackSecuritySettings.apply + } + } + + private def lift[T](value: => T): EitherT[Task, LoadingError, T] = EitherT.rightT(value) +} + +sealed trait SslSettings { + def serverCertificateSettings: SslSettings.ServerCertificateSettings + + def clientCertificateSettings: Option[SslSettings.ClientCertificateSettings] + + def allowedProtocols: Set[SslSettings.Protocol] + + def allowedCiphers: Set[SslSettings.Cipher] + + def clientAuthenticationEnabled: Boolean + + def certificateVerificationEnabled: Boolean + + def fipsMode: FipsMode +} + +object SslSettings { + + final case class KeystorePassword(value: String) + final case class KeystoreFile(value: File) + final case class TruststorePassword(value: String) + final case class TruststoreFile(value: File) + final case class ServerCertificateKeyFile(value: File) + final case class ServerCertificateFile(value: File) + final case class ClientTrustedCertificateFile(value: File) + final case class KeyPass(value: String) + final case class KeyAlias(value: String) + final case class Cipher(value: String) + final case class Protocol(value: String) + + sealed trait ServerCertificateSettings + object ServerCertificateSettings { + final case class KeystoreBasedSettings(keystoreFile: KeystoreFile, + keystorePassword: Option[KeystorePassword], + keyAlias: Option[KeyAlias], + keyPass: Option[KeyPass]) + extends ServerCertificateSettings + final case class FileBasedSettings(serverCertificateKeyFile: ServerCertificateKeyFile, + serverCertificateFile: ServerCertificateFile) + extends ServerCertificateSettings + } + + sealed trait ClientCertificateSettings + object ClientCertificateSettings { + final case class TruststoreBasedSettings(truststoreFile: TruststoreFile, + truststorePassword: Option[TruststorePassword]) + extends ClientCertificateSettings + final case class FileBasedSettings(clientTrustedCertificateFile: ClientTrustedCertificateFile) + extends ClientCertificateSettings + } + + final case class ExternalSslSettings(serverCertificateSettings: ServerCertificateSettings, + clientCertificateSettings: Option[ClientCertificateSettings], + allowedProtocols: Set[SslSettings.Protocol], + allowedCiphers: Set[SslSettings.Cipher], + clientAuthenticationEnabled: Boolean, + fipsMode: FipsMode) + extends SslSettings { + + override val certificateVerificationEnabled: Boolean = false + } + + final case class InternodeSslSettings(serverCertificateSettings: ServerCertificateSettings, + clientCertificateSettings: Option[ClientCertificateSettings], + allowedProtocols: Set[SslSettings.Protocol], + allowedCiphers: Set[SslSettings.Cipher], + clientAuthenticationEnabled: Boolean, + certificateVerificationEnabled: Boolean, + hostnameVerificationEnabled: Boolean, + fipsMode: FipsMode) + extends SslSettings + + sealed trait FipsMode + object FipsMode { + case object NonFips extends FipsMode + case object SslOnly extends FipsMode + } + +} + +private object SslDecoders extends Logging { + + object consts { + val rorSection = "readonlyrest" + val fipsMode = "fips_mode" + val externalSsl = "ssl" + val internodeSsl = "ssl_internode" + val keystoreFile = "keystore_file" + val keystorePass = "keystore_pass" + val truststoreFile = "truststore_file" + val truststorePass = "truststore_pass" + val keyPass = "key_pass" + val keyAlias = "key_alias" + val allowedCiphers = "allowed_ciphers" + val allowedProtocols = "allowed_protocols" + val certificateVerification = "certificate_verification" + val hostnameVerification = "hostname_verification" + val clientAuthentication = "client_authentication" + val verification = "verification" + val enable = "enable" + val serverCertificateKeyFile = "server_certificate_key_file" + val serverCertificateFile = "server_certificate_file" + val clientTrustedCertificateFile = "client_trusted_certificate_file" + } + + final case class CommonSslProperties(serverCertificateSettings: ServerCertificateSettings, + clientCertificateSettings: Option[ClientCertificateSettings], + allowedProtocols: Set[SslSettings.Protocol], + allowedCiphers: Set[SslSettings.Cipher], + clientAuthentication: Option[Boolean]) + + private implicit val keystorePasswordDecoder: Decoder[KeystorePassword] = DecoderHelpers.decodeStringLike.map(KeystorePassword.apply) + private implicit val truststorePasswordDecoder: Decoder[TruststorePassword] = DecoderHelpers.decodeStringLike.map(TruststorePassword.apply) + private implicit val keyPassDecoder: Decoder[KeyPass] = DecoderHelpers.decodeStringLike.map(KeyPass.apply) + private implicit val keyAliasDecoder: Decoder[KeyAlias] = DecoderHelpers.decodeStringLike.map(KeyAlias.apply) + private implicit val cipherDecoder: Decoder[Cipher] = DecoderHelpers.decodeStringLike.map(Cipher.apply) + private implicit val protocolDecoder: Decoder[Protocol] = DecoderHelpers.decodeStringLike.map(Protocol.apply) + + private def clientCertificateSettingsDecoder(basePath: File): Decoder[Option[ClientCertificateSettings]] = { + val aFileDecoder: Decoder[File] = fileDecoder(basePath) + implicit val truststoreFileDecoder = aFileDecoder.map(TruststoreFile.apply) + implicit val clientTrustedCertificateFileDecoder = aFileDecoder.map(ClientTrustedCertificateFile.apply) + + val truststoreBasedClientCertificateSettingsDecoder: Decoder[ClientCertificateSettings] = + Decoder.forProduct2(consts.truststoreFile, consts.truststorePass)(ClientCertificateSettings.TruststoreBasedSettings.apply) + val fileBasedClientCertificateSettingsDecoder: Decoder[ClientCertificateSettings] = + Decoder.forProduct1(consts.clientTrustedCertificateFile)(ClientCertificateSettings.FileBasedSettings.apply) + Decoder.instance { c => + val truststoreBasedKeys = Set(consts.truststoreFile, consts.truststorePass) + val fileBasedKeys = Set(consts.clientTrustedCertificateFile) + val presentKeys = c.keys.fold[Set[String]](Set.empty)(_.toSet) + if (presentKeys.intersect(truststoreBasedKeys).nonEmpty && presentKeys.intersect(fileBasedKeys).nonEmpty) { + val errorMessage = s"Field sets [${fileBasedKeys.show}] and [${truststoreBasedKeys.show}] could not be present in the same settings section" + logger.error(errorMessage) + Left(DecodingFailure(errorMessage, List.empty)) + } else if (presentKeys.intersect(truststoreBasedKeys).nonEmpty) { + truststoreBasedClientCertificateSettingsDecoder(c) + .map(Option.apply) + } else if (presentKeys.intersect(fileBasedKeys).nonEmpty) { + if (SSLCertHelper.isPEMHandlingAvailable) { + fileBasedClientCertificateSettingsDecoder(c) + .map(Option.apply) + } else { + val errorMessage = "PEM File Handling is not available in your current deployment of Elasticsearch" + logger.error(errorMessage) + Left(DecodingFailure(errorMessage, List.empty)) + } + } else { + Right(None) + } + } + } + + private def serverCertificateSettingsDecoder(basePath: File): Decoder[ServerCertificateSettings] = { + val aFileDecoder: Decoder[File] = fileDecoder(basePath) + implicit val keystoreFileDecoder = aFileDecoder.map(KeystoreFile.apply) + implicit val serverCertificateFileDecoder = aFileDecoder.map(ServerCertificateFile.apply) + implicit val serverCertificateKeyFileDecoder = aFileDecoder.map(ServerCertificateKeyFile.apply) + val keystoreBasedServerCertificateSettingsDecoder: Decoder[ServerCertificateSettings] = + Decoder.forProduct4(consts.keystoreFile, consts.keystorePass, consts.keyAlias, consts.keyPass)(ServerCertificateSettings.KeystoreBasedSettings.apply) + val fileBasedServerCertificateSettingsDecoder: Decoder[ServerCertificateSettings] = + Decoder.forProduct2(consts.serverCertificateKeyFile, consts.serverCertificateFile)(ServerCertificateSettings.FileBasedSettings.apply) + Decoder.instance { c => + val keystoreBasedKeys = Set(consts.keystoreFile, consts.keystorePass, consts.keyPass, consts.keyAlias) + val fileBasedKeys = Set(consts.serverCertificateKeyFile, consts.serverCertificateFile) + val presentKeys = c.keys.fold[Set[String]](Set.empty)(_.toSet) + if (presentKeys.intersect(keystoreBasedKeys).nonEmpty && presentKeys.intersect(fileBasedKeys).nonEmpty) { + val errorMessage = s"Field sets [${fileBasedKeys.show}] and [${keystoreBasedKeys.show}] could not be present in the same settings section" + logger.error(errorMessage) + Left(DecodingFailure(errorMessage, List.empty)) + } else if (presentKeys.intersect(keystoreBasedKeys).nonEmpty) { + keystoreBasedServerCertificateSettingsDecoder(c) + } else if (presentKeys.intersect(fileBasedKeys).nonEmpty) { + if (SSLCertHelper.isPEMHandlingAvailable) { + fileBasedServerCertificateSettingsDecoder(c) + } else { + val errorMessage = "PEM File Handling is not available in your current deployment of Elasticsearch" + logger.error(errorMessage) + Left(DecodingFailure(errorMessage, List.empty)) + } + } else { + val errorMessage = "There was no SSL settings present for server" + logger.error(errorMessage) + Left(DecodingFailure(errorMessage, List.empty)) + } + } + } + + def rorSslDecoder(basePath: File): Decoder[Option[RorSslSettings]] = Decoder.instance { c => + implicit val isFipsCompliantDecoder: Decoder[FipsMode] = Decoder.decodeString.emap { + case "NON_FIPS" => Right(FipsMode.NonFips) + case "SSL_ONLY" => Right(FipsMode.SslOnly) + case _ => Left("Invalid settings option for FIPS MODE. Valid values are: NON_FIPS, SSL_ONLY") + } + for { + fipsMode <- c.downField(consts.rorSection).downField(consts.fipsMode).as[Option[FipsMode]] + interNodeSsl <- { + implicit val internodeSslSettingsDecoder: Decoder[Option[InternodeSslSettings]] = + sslInternodeSettingsDecoder(basePath, fipsMode.getOrElse(FipsMode.NonFips)) + c.downField(consts.rorSection).downField(consts.internodeSsl).as[Option[Option[InternodeSslSettings]]] + } + externalSsl <- { + implicit val externalSslSettingsDecoder: Decoder[Option[ExternalSslSettings]] = + sslExternalSettingsDecoder(basePath, fipsMode.getOrElse(FipsMode.NonFips)) + c.downField(consts.rorSection).downField(consts.externalSsl).as[Option[Option[ExternalSslSettings]]] + } + } yield { + (externalSsl.flatten, interNodeSsl.flatten) match { + case (Some(ssl), None) => Some(RorSslSettings.OnlyExternalSslSettings(ssl)) + case (None, Some(ssl)) => Some(RorSslSettings.OnlyInternodeSslSettings(ssl)) + case (Some(externalSsl), Some(internalSsl)) => Some(RorSslSettings.ExternalAndInternodeSslSettings(externalSsl, internalSsl)) + case (None, None) => None + } + } + } + + private def sslInternodeSettingsDecoder(basePath: File, + fipsMode: FipsMode): Decoder[Option[InternodeSslSettings]] = Decoder.instance { c => + whenEnabled(c) { + for { + certificateVerification <- c.downField(consts.certificateVerification).as[Option[Boolean]] + hostnameVerification <- c.downField(consts.hostnameVerification).as[Option[Boolean]] + verification <- c.downField(consts.verification).as[Option[Boolean]] + sslCommonProperties <- sslCommonPropertiesDecoder(basePath, c) + } yield + InternodeSslSettings( + serverCertificateSettings = sslCommonProperties.serverCertificateSettings, + clientCertificateSettings = sslCommonProperties.clientCertificateSettings, + allowedProtocols = sslCommonProperties.allowedProtocols, + allowedCiphers = sslCommonProperties.allowedCiphers, + clientAuthenticationEnabled = sslCommonProperties.clientAuthentication.getOrElse(false), + certificateVerificationEnabled = certificateVerification.orElse(verification).getOrElse(false), + hostnameVerificationEnabled = hostnameVerification.getOrElse(false), + fipsMode = fipsMode + ) + } + } + + private def sslExternalSettingsDecoder(basePath: File, + fipsMode: FipsMode): Decoder[Option[ExternalSslSettings]] = Decoder.instance { c => + whenEnabled(c) { + for { + verification <- c.downField(consts.verification).as[Option[Boolean]] + sslCommonProperties <- sslCommonPropertiesDecoder(basePath, c) + } yield + ExternalSslSettings( + serverCertificateSettings = sslCommonProperties.serverCertificateSettings, + clientCertificateSettings = sslCommonProperties.clientCertificateSettings, + allowedProtocols = sslCommonProperties.allowedProtocols, + allowedCiphers = sslCommonProperties.allowedCiphers, + clientAuthenticationEnabled = sslCommonProperties.clientAuthentication.orElse(verification).getOrElse(false), + fipsMode = fipsMode + ) + } + } + + private def sslCommonPropertiesDecoder(basePath: File, c: HCursor) = { + for { + ciphers <- c.downField(consts.allowedCiphers).as[Option[Set[Cipher]]] + protocols <- c.downField(consts.allowedProtocols).as[Option[Set[Protocol]]] + clientAuthentication <- c.downField(consts.clientAuthentication).as[Option[Boolean]] + serverCertificateSettings <- serverCertificateSettingsDecoder(basePath).apply(c) + clientCertificateSettings <- clientCertificateSettingsDecoder(basePath).apply(c) + } yield + CommonSslProperties( + serverCertificateSettings = serverCertificateSettings, + clientCertificateSettings = clientCertificateSettings, + allowedProtocols = protocols.getOrElse(Set.empty[Protocol]), + allowedCiphers = ciphers.getOrElse(Set.empty[Cipher]), + clientAuthentication = clientAuthentication, + ) + } + + private def whenEnabled[T <: SslSettings](cursor: HCursor)(decoding: => Either[DecodingFailure, T]) = { + for { + isEnabled <- cursor.downField(consts.enable).as[Option[Boolean]] + result <- if (isEnabled.getOrElse(true)) decoding.map(Some.apply) else Right(None) + } yield result + } + + private def fileDecoder(basePath: File): Decoder[File] = + Decoder.decodeString.map { str => basePath / str } +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/es/YamlFileBasedSettingsLoader.scala b/core/src/main/scala/tech/beshu/ror/settings/es/YamlFileBasedSettingsLoader.scala new file mode 100644 index 0000000000..6916fde703 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/es/YamlFileBasedSettingsLoader.scala @@ -0,0 +1,98 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.es + +import better.files.File +import io.circe.{Decoder, DecodingFailure, Json} +import monix.eval.Task +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.blocks.variables.transformation.TransformationCompiler +import tech.beshu.ror.accesscontrol.factory.JsonStaticVariablesResolver +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError +import tech.beshu.ror.utils.yaml.YamlOps.jsonWithOneLinerKeysToRegularJson +import tech.beshu.ror.utils.yaml.YamlParser + +final class YamlFileBasedSettingsLoader(file: File) + (implicit systemContext: SystemContext) { + + private val yamlParser: YamlParser = new YamlParser() + + private val jsonStaticVariableResolver = new JsonStaticVariablesResolver( + systemContext.envVarsProvider, + TransformationCompiler.withoutAliases(systemContext.variablesFunctions) + ) + + def loadSettings[SETTINGS: Decoder](settingsName: String): Task[Either[LoadingError, SETTINGS]] = Task.delay { + for { + _ <- Either.cond(file.exists, (), LoadingError.FileNotFound(file): LoadingError) + settings <- loadedSettingsJson + .flatMap { json => + implicitly[Decoder[SETTINGS]] + .decodeJson(json) + .left.map(e => createError(s"Cannot load ${settingsName.show} from file ${file.pathAsString.show}. Cause: ${prettyCause(e).show}")) + } + } yield settings + } + + private lazy val loadedSettingsJson: Either[LoadingError, Json] = { + file.fileReader { reader => + yamlParser + .parse(reader) + .left.map(e => createError(s"Cannot parse file ${file.pathAsString.show} content. Cause: ${e.message.show}")) + .flatMap { json => + jsonStaticVariableResolver + .resolve(json) + .left.map(e => createError(s"Unable to resolve environment variables for file ${file.pathAsString.show}. $e.")) + } + .map(jsonWithOneLinerKeysToRegularJson) + } + } + + private def prettyCause(error: DecodingFailure) = { + error.message match { + case message if message.startsWith("DecodingFailure at") => "yaml is malformed" + case other => other + } + } + + private def createError(message: String) = LoadingError.MalformedSettings(file, message) +} + +object YamlFileBasedSettingsLoader { + sealed trait LoadingError + object LoadingError { + final case class FileNotFound(file: File) extends LoadingError + final case class MalformedSettings(file: File, message: String) extends LoadingError + } +} + +private[es] trait YamlFileBasedSettingsLoaderSupport { + + protected def loadSetting[T: Decoder](esEnv: EsEnv, settingsName: String) + (implicit systemContext: SystemContext): Task[Either[LoadingError, T]] = { + loadSetting(esEnv.elasticsearchConfig.file, settingsName) + } + + protected def loadSetting[T: Decoder](file: File, settingsName: String) + (implicit systemContext: SystemContext): Task[Either[LoadingError, T]] = { + val loader = new YamlFileBasedSettingsLoader(file) + loader.loadSettings[T](settingsName) + } + +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/MainRorSettings.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/MainRorSettings.scala new file mode 100644 index 0000000000..17911989fc --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/MainRorSettings.scala @@ -0,0 +1,19 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror + +final case class MainRorSettings(rawSettings: RawRorSettings) diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/RawRorSettings.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/RawRorSettings.scala new file mode 100644 index 0000000000..32bb338b81 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/RawRorSettings.scala @@ -0,0 +1,25 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror + +import cats.Eq +import io.circe.Json + +final case class RawRorSettings(settingsJson: Json, rawYaml: String) +object RawRorSettings { + implicit val eq: Eq[RawRorSettings] = Eq.fromUniversalEquals +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/RawRorSettingsYamlParser.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/RawRorSettingsYamlParser.scala new file mode 100644 index 0000000000..7f91110ea4 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/RawRorSettingsYamlParser.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror + +import better.files.File +import io.circe.{Json, ParsingFailure} +import squants.information.Information +import tech.beshu.ror.settings.ror.RawRorSettingsYamlParser.ParsingRorSettingsError +import tech.beshu.ror.settings.ror.RawRorSettingsYamlParser.ParsingRorSettingsError.{InvalidContent, MoreThanOneRorSection, NoRorSection} +import tech.beshu.ror.utils.yaml.YamlParser + +class RawRorSettingsYamlParser(maxSize: Information) { + + private val yamlParser: YamlParser = new YamlParser(Some(maxSize)) + + def fromFile(file: File): Either[ParsingRorSettingsError, RawRorSettings] = { + fromString(file.contentAsString) + } + + def fromString(content: String): Either[ParsingRorSettingsError, RawRorSettings] = { + handleParseResult(yamlParser.parse(content)) + .map(RawRorSettings(_, content)) + } + + private def handleParseResult(result: Either[ParsingFailure, Json]) = { + result + .left.map(InvalidContent.apply) + .flatMap { json => validateRorJson(json) } + } + + private def validateRorJson(json: Json) = { + json \\ "readonlyrest" match { + case Nil => Left(NoRorSection) + case _ :: Nil => Right(json) + case _ => Left(MoreThanOneRorSection) + } + } +} +object RawRorSettingsYamlParser { + + sealed trait ParsingRorSettingsError + object ParsingRorSettingsError { + case object NoRorSection extends ParsingRorSettingsError + case object MoreThanOneRorSection extends ParsingRorSettingsError + final case class InvalidContent(throwable: Throwable) extends ParsingRorSettingsError + } +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/TestRorSettings.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/TestRorSettings.scala new file mode 100644 index 0000000000..729ee0260a --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/TestRorSettings.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror + +import tech.beshu.ror.accesscontrol.blocks.mocks.AuthServicesMocks +import tech.beshu.ror.settings.ror.TestRorSettings.Expiration +import tech.beshu.ror.utils.DurationOps.PositiveFiniteDuration + +import java.time.{Clock, Instant} + +final case class TestRorSettings(rawSettings: RawRorSettings, + mocks: AuthServicesMocks, + expiration: Expiration) { + def isExpired(clock: Clock): Boolean = { + expiration.validTo.isBefore(clock.instant()) + } +} + +object TestRorSettings { + final case class Expiration(ttl: PositiveFiniteDuration, validTo: Instant) +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/loader/ForceLoadRorSettingsFromFileLoader.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/loader/ForceLoadRorSettingsFromFileLoader.scala new file mode 100644 index 0000000000..1ba99f0c5b --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/loader/ForceLoadRorSettingsFromFileLoader.scala @@ -0,0 +1,36 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.loader + +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.ror.source.FileSettingsSource +import tech.beshu.ror.settings.ror.{MainRorSettings, TestRorSettings} + +class ForceLoadRorSettingsFromFileLoader(mainSettingsFileSource: FileSettingsSource[MainRorSettings]) + extends StartingRorSettingsLoader with Logging { + + override def load(): Task[Either[LoadingError, (MainRorSettings, Option[TestRorSettings])]] = { + val result = loadSettingsFromSource( + source = mainSettingsFileSource, + settingsDescription = s"main settings from file '${mainSettingsFileSource.settingsFile.show}''" + ) + result.map((_, None)).value + } + +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/loader/RetryStrategy.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/loader/RetryStrategy.scala new file mode 100644 index 0000000000..b8434813e9 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/loader/RetryStrategy.scala @@ -0,0 +1,67 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.loader + +import cats.Show +import cats.data.EitherT +import cats.implicits.toShow +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.RorCoreSettingsLoadingStrategy.LoadingRetryStrategySettings + +trait RetryStrategy { + def withRetry[ERROR: Show, RESULT](operation: Task[Either[ERROR, RESULT]], + operationDescription: String): Task[Either[ERROR, RESULT]] + def withRetryT[ERROR: Show, RESULT](operation: EitherT[Task, ERROR, RESULT], + operationDescription: String): EitherT[Task, ERROR, RESULT] = + EitherT(withRetry(operation.value, operationDescription)) +} + +class ConfigurableRetryStrategy(config: LoadingRetryStrategySettings) + extends RetryStrategy with Logging { + + override def withRetry[ERROR: Show, RESULT](operation: Task[Either[ERROR, RESULT]], + operationDescription: String): Task[Either[ERROR, RESULT]] = + attemptWithRetry(operation, currentAttempt = 1, config.attemptsCount.value.value, operationDescription) + + private def attemptWithRetry[ERROR : Show, A](operation: Task[Either[ERROR, A]], + currentAttempt: Int, + maxAttempts: Int, + operationDescription: String): Task[Either[ERROR, A]] = { + val delay = if (currentAttempt == 1) config.delay.value.value else config.attemptsInterval.value.value + for { + _ <- Task.sleep(delay) + result <- operation + finalResult <- result match { + case Right(value) => + Task.now(Right(value)) + case Left(error) if shouldRetry(currentAttempt, maxAttempts) => + logger.debug(s"$operationDescription - retry attempt $currentAttempt/$maxAttempts failed. Retrying in ${config.attemptsInterval.show}...") + attemptWithRetry(operation, currentAttempt + 1, maxAttempts, operationDescription) + case Left(error) => + logger.debug(s"$operationDescription - failed permanently after $currentAttempt attempts: ${error.show}") + Task.now(Left(error)) + } + } yield finalResult + } + + private def shouldRetry(currentAttempt: Int, maxAttempts: Int): Boolean = { + currentAttempt < maxAttempts + } + +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/loader/RetryableIndexSourceWithFileSourceFallbackRorSettingsLoader.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/loader/RetryableIndexSourceWithFileSourceFallbackRorSettingsLoader.scala new file mode 100644 index 0000000000..8426e9ed0d --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/loader/RetryableIndexSourceWithFileSourceFallbackRorSettingsLoader.scala @@ -0,0 +1,67 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.loader + +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.ror.source.* +import tech.beshu.ror.settings.ror.{MainRorSettings, TestRorSettings} + +class RetryableIndexSourceWithFileSourceFallbackRorSettingsLoader(mainSettingsIndexSource: MainSettingsIndexSource, + mainSettingsIndexLoadingRetryStrategy: RetryStrategy, + mainSettingsFileSource: MainSettingsFileSource, + testSettingsIndexSource: TestSettingsIndexSource) + extends StartingRorSettingsLoader with Logging { + + override def load(): Task[Either[LoadingError, (MainRorSettings, Option[TestRorSettings])]] = { + val result = for { + mainSettings <- mainSettingsIndexLoadingRetryStrategy + .withRetryT( + operation = loadMainSettingsFromIndex(), + operationDescription = s"Loading ReadonlyREST main settings from index '${mainSettingsIndexSource.settingsIndex.show}'" + ) + .orElse(loadMainSettingsFromFile()) + testSettings <- loadTestSettingsFromIndex() + .map(Option.apply) + .recover { case _ => Option.empty[TestRorSettings] } + } yield (mainSettings, testSettings) + result.value + } + + private def loadMainSettingsFromIndex() = { + loadSettingsFromSource( + source = mainSettingsIndexSource, + settingsDescription = s"main settings from index '${mainSettingsIndexSource.settingsIndex.show}'" + ) + } + + private def loadMainSettingsFromFile() = { + loadSettingsFromSource( + source = mainSettingsFileSource, + settingsDescription = s"main settings from file '${mainSettingsFileSource.settingsFile.show}''" + ) + } + + private def loadTestSettingsFromIndex() = { + loadSettingsFromSource( + source = testSettingsIndexSource, + settingsDescription = s"test settings from index '${testSettingsIndexSource.settingsIndex.show}'" + ) + } + +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/loader/StartingRorSettingsLoader.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/loader/StartingRorSettingsLoader.scala new file mode 100644 index 0000000000..5b7aa0799d --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/loader/StartingRorSettingsLoader.scala @@ -0,0 +1,47 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.loader + +import cats.Show +import cats.data.EitherT +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource +import tech.beshu.ror.settings.ror.{MainRorSettings, TestRorSettings} +import tech.beshu.ror.utils.ScalaOps.{EitherTOps, LoggerOps} + +trait StartingRorSettingsLoader { + this: Logging => + + def load(): Task[Either[LoadingError, (MainRorSettings, Option[TestRorSettings])]] + + protected def loadSettingsFromSource[S: Show, E: Show](source: ReadOnlySettingsSource[S, E], + settingsDescription: String): EitherT[Task, LoadingError, S] = { + for { + _ <- EitherT.liftTask(logger.info(s"Loading ReadonlyREST $settingsDescription ...")) + loadedSettings <- EitherT(source.load()) + .biSemiflatTap( + error => logger.dInfo(s"Loading ReadonlyREST $settingsDescription failed: ${error.show}"), + settings => logger.dDebug(s"Loaded ReadonlyREST $settingsDescription:\n${settings.show}") + ) + .leftMap(error => error.show) + } yield loadedSettings + } + + type LoadingError = String +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/source/FileSettingsSource.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/source/FileSettingsSource.scala new file mode 100644 index 0000000000..8f9405355e --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/source/FileSettingsSource.scala @@ -0,0 +1,59 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.source + +import better.files.File +import cats.data.EitherT +import io.circe.{Decoder, Json} +import monix.eval.Task +import tech.beshu.ror.settings.ror.source.FileSettingsSource.FileSettingsLoadingError +import tech.beshu.ror.settings.ror.source.FileSettingsSource.LoadingError.FileNotExist +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource.SettingsLoadingError +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource.SettingsLoadingError.SourceSpecificError + +class FileSettingsSource[SETTINGS: Decoder](val settingsFile: File) + extends ReadOnlySettingsSource[SETTINGS, FileSettingsSource.LoadingError] { + + override def load(): Task[Either[FileSettingsLoadingError, SETTINGS]] = { + (for { + _ <- checkIfFileExist(settingsFile) + settings <- loadSettingsFromFile(settingsFile) + } yield settings).value + } + + private def checkIfFileExist(file: File): EitherT[Task, FileSettingsLoadingError, File] = + EitherT.cond(file.exists, file, SourceSpecificError(FileNotExist(file))) + + private def loadSettingsFromFile(file: File): EitherT[Task, FileSettingsLoadingError, SETTINGS] = { + EitherT + .pure[Task, FileSettingsLoadingError](file.contentAsString) + .subflatMap { raw => + Json + .fromString(raw).as[SETTINGS] + .left.map { failure => SettingsLoadingError.SettingsMalformed(failure.message) } + } + } +} +object FileSettingsSource { + + type FileSettingsLoadingError = SettingsLoadingError[LoadingError] + + sealed trait LoadingError + object LoadingError { + final case class FileNotExist(file: File) extends LoadingError + } +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/source/IndexSettingsSource.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/source/IndexSettingsSource.scala new file mode 100644 index 0000000000..a3f08989e2 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/source/IndexSettingsSource.scala @@ -0,0 +1,80 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.source + +import io.circe.syntax.* +import io.circe.{Decoder, Encoder} +import monix.eval.Task +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.CannotWriteToIndex +import tech.beshu.ror.settings.ror.source.IndexSettingsSource.LoadingError.{DocumentNotFound, IndexNotFound} +import tech.beshu.ror.settings.ror.source.IndexSettingsSource.SavingError.CannotSaveSettings +import tech.beshu.ror.settings.ror.source.IndexSettingsSource.{IndexSettingsLoadingError, IndexSettingsSavingError, LoadingError, SavingError} +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource.SettingsLoadingError +import tech.beshu.ror.settings.ror.source.ReadWriteSettingsSource.SettingsSavingError + +class IndexSettingsSource[SETTINGS: Encoder : Decoder](indexDocumentManager: IndexDocumentManager, + val settingsIndex: IndexName.Full, + documentId: String) + extends ReadWriteSettingsSource[SETTINGS, LoadingError, SavingError] { + + override def load(): Task[Either[IndexSettingsLoadingError, SETTINGS]] = { + indexDocumentManager + .documentAsJson(settingsIndex, documentId) + .map { + case Right(document) => + document.as[SETTINGS] + .left.map { decodingFailure => + SettingsLoadingError.SettingsMalformed(decodingFailure.message) + } + case Left(IndexDocumentManager.IndexNotFound) => + settingsLoaderError(IndexNotFound) + case Left(IndexDocumentManager.DocumentNotFound | IndexDocumentManager.DocumentUnreachable) => + settingsLoaderError(DocumentNotFound) + } + } + + override def save(settings: SETTINGS): Task[Either[IndexSettingsSavingError, Unit]] = { + indexDocumentManager + .saveDocumentJson(settingsIndex, documentId, settings.asJson) + .map { + _.left.map { case CannotWriteToIndex => SettingsSavingError.SourceSpecificError(CannotSaveSettings) } + } + } + + private def settingsLoaderError(error: LoadingError) = + Left(SettingsLoadingError.SourceSpecificError(error)) + +} +object IndexSettingsSource { + + type IndexSettingsLoadingError = SettingsLoadingError[LoadingError] + + sealed trait LoadingError + object LoadingError { + case object IndexNotFound extends LoadingError + case object DocumentNotFound extends LoadingError + } + + type IndexSettingsSavingError = SettingsSavingError[SavingError] + + sealed trait SavingError + object SavingError { + case object CannotSaveSettings extends SavingError + } +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/source/MainSettingsFileSource.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/source/MainSettingsFileSource.scala new file mode 100644 index 0000000000..51fb0db664 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/source/MainSettingsFileSource.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.source + +import io.circe.Decoder +import tech.beshu.ror.accesscontrol.domain.RorSettingsFile +import tech.beshu.ror.settings.ror.{MainRorSettings, RawRorSettingsYamlParser} + +class MainSettingsFileSource private (settingsFile: RorSettingsFile) + (implicit decoder: Decoder[MainRorSettings]) + extends FileSettingsSource[MainRorSettings](settingsFile.file) + +object MainSettingsFileSource { + + def create(settingsFile: RorSettingsFile, + settingsYamlParser: RawRorSettingsYamlParser): MainSettingsFileSource = { + implicit val decoder: Decoder[MainRorSettings] = + new RawRorSettingsCodec(settingsYamlParser).map(MainRorSettings.apply) + new MainSettingsFileSource(settingsFile) + } +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/source/MainSettingsIndexSource.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/source/MainSettingsIndexSource.scala new file mode 100644 index 0000000000..e07cba0d7d --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/source/MainSettingsIndexSource.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.source + +import io.circe.Codec +import tech.beshu.ror.accesscontrol.domain.RorSettingsIndex +import tech.beshu.ror.settings.ror.{MainRorSettings, RawRorSettings, RawRorSettingsYamlParser} +import tech.beshu.ror.es.IndexDocumentManager +import MainSettingsIndexSource.Const + +class MainSettingsIndexSource private(indexDocumentManager: IndexDocumentManager, + settingsIndex: RorSettingsIndex) + (implicit codec: Codec[MainRorSettings]) + extends IndexSettingsSource[MainRorSettings](indexDocumentManager, settingsIndex.index, documentId = Const.id) + +object MainSettingsIndexSource { + + def create(indexJsonContentService: IndexDocumentManager, + settingsIndex: RorSettingsIndex, + settingsYamlParser: RawRorSettingsYamlParser): MainSettingsIndexSource = { + implicit val codec: Codec[MainRorSettings] = mainRorSettingsCodec(settingsYamlParser) + new MainSettingsIndexSource(indexJsonContentService, settingsIndex) + } + + private object Const { + val id = "1" + val settingsKey = "settings" + } + + private def mainRorSettingsCodec(settingsYamlParser: RawRorSettingsYamlParser): Codec[MainRorSettings] = { + implicit val codec: Codec[RawRorSettings] = new RawRorSettingsCodec(settingsYamlParser) + Codec.forProduct1[MainRorSettings, RawRorSettings](Const.settingsKey)(MainRorSettings.apply)(_.rawSettings) + } +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/source/RawRorSettingsCodec.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/source/RawRorSettingsCodec.scala new file mode 100644 index 0000000000..b8bdaaddba --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/source/RawRorSettingsCodec.scala @@ -0,0 +1,40 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.source + +import cats.implicits.* +import io.circe.Decoder.Result +import io.circe.{Codec, Decoder, HCursor, Json} +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.ror.{RawRorSettings, RawRorSettingsYamlParser} + +private [source] class RawRorSettingsCodec(yamlParser: RawRorSettingsYamlParser) + extends Codec[RawRorSettings] { + + override def apply(c: HCursor): Result[RawRorSettings] = + Decoder + .decodeString + .emap { str => + yamlParser + .fromString(str) + .left.map(_.show) + } + .apply(c) + + override def apply(a: RawRorSettings): Json = + Json.fromString(a.rawYaml) +} diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/source/SettingsSource.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/source/SettingsSource.scala new file mode 100644 index 0000000000..3618a87e04 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/source/SettingsSource.scala @@ -0,0 +1,47 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.source + +import io.circe.{Decoder, Encoder} +import monix.eval.Task +import tech.beshu.ror.settings.ror.source.ReadOnlySettingsSource.SettingsLoadingError +import tech.beshu.ror.settings.ror.source.ReadWriteSettingsSource.SettingsSavingError + +sealed trait SettingsSource[SETTINGS] + +trait ReadOnlySettingsSource[SETTINGS: Decoder, ERROR] extends SettingsSource[SETTINGS] { + def load(): Task[Either[SettingsLoadingError[ERROR], SETTINGS]] +} +object ReadOnlySettingsSource { + sealed trait SettingsLoadingError[+ERROR] + object SettingsLoadingError { + final case class SettingsMalformed(cause: String) extends SettingsLoadingError[Nothing] + final case class SourceSpecificError[ERROR](error: ERROR) extends SettingsLoadingError[ERROR] + } +} + +trait ReadWriteSettingsSource[SETTINGS: Encoder : Decoder, READ_SPECIFIC_ERROR, WRITE_SPECIFIC_ERROR] + extends ReadOnlySettingsSource[SETTINGS, READ_SPECIFIC_ERROR] { + + def save(settings: SETTINGS): Task[Either[SettingsSavingError[WRITE_SPECIFIC_ERROR], Unit]] +} +object ReadWriteSettingsSource { + sealed trait SettingsSavingError[+ERROR] + object SettingsSavingError { + final case class SourceSpecificError[ERROR](error: ERROR) extends SettingsSavingError[ERROR] + } +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/settings/ror/source/TestSettingsIndexSource.scala b/core/src/main/scala/tech/beshu/ror/settings/ror/source/TestSettingsIndexSource.scala new file mode 100644 index 0000000000..143e276b75 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/settings/ror/source/TestSettingsIndexSource.scala @@ -0,0 +1,194 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.settings.ror.source + +import eu.timepit.refined.types.string.NonEmptyString +import io.circe.{Codec, Decoder, Encoder} +import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.LdapService +import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationService, ExternalAuthorizationService} +import tech.beshu.ror.accesscontrol.blocks.mocks.AuthServicesMocks +import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.ExternalAuthenticationServiceMock.ExternalAuthenticationUserMock +import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.ExternalAuthorizationServiceMock.ExternalAuthorizationServiceUserMock +import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.LdapServiceMock.LdapUserMock +import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.{ExternalAuthenticationServiceMock, ExternalAuthorizationServiceMock, LdapServiceMock} +import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId +import tech.beshu.ror.accesscontrol.domain.{Group, GroupName, RorSettingsIndex, User} +import tech.beshu.ror.settings.ror.TestRorSettings.Expiration +import tech.beshu.ror.settings.ror.{RawRorSettings, RawRorSettingsYamlParser, TestRorSettings} +import tech.beshu.ror.es.IndexDocumentManager +import TestSettingsIndexSource.Const +import tech.beshu.ror.syntax.* +import tech.beshu.ror.utils.DurationOps.* +import tech.beshu.ror.utils.json.KeyCodec + +import java.time.format.DateTimeFormatter +import java.time.{Instant, ZoneOffset} +import scala.concurrent.duration.Duration +import scala.util.Try + +class TestSettingsIndexSource private(indexDocumentManager: IndexDocumentManager, + settingsIndex: RorSettingsIndex) + (implicit codec: Codec[TestRorSettings]) + extends IndexSettingsSource[TestRorSettings](indexDocumentManager, settingsIndex.index, documentId = Const.id) + +object TestSettingsIndexSource { + + def create(indexDocumentManager: IndexDocumentManager, + settingsIndex: RorSettingsIndex, + settingsYamlParser: RawRorSettingsYamlParser): TestSettingsIndexSource = { + implicit val codec: Codec[TestRorSettings] = createTestRorSettingsCodec(settingsYamlParser) + new TestSettingsIndexSource(indexDocumentManager, settingsIndex) + } + + private object Const { + val id = "2" + object properties { + val settings = "settings" + val expirationTtl = "expiration_ttl_millis" + val expirationTime = "expiration_timestamp" + val mocks = "auth_services_mocks" + } + } + + private def createTestRorSettingsCodec(yamlParser: RawRorSettingsYamlParser): Codec[TestRorSettings] = + Codec.from(decoder(yamlParser), encoder(yamlParser)) + + import Const.* + + private def encoder(implicit yamlParser: RawRorSettingsYamlParser): Encoder[TestRorSettings] = + Encoder.forProduct4(properties.settings, properties.mocks, properties.expirationTtl, properties.expirationTime){ + testRorSettings => ( + testRorSettings.rawSettings, + testRorSettings.mocks, + testRorSettings.expiration.ttl, + testRorSettings.expiration.validTo + ) + } + + private def decoder(implicit yamlParser: RawRorSettingsYamlParser): Decoder[TestRorSettings] = Decoder.instance { c => + for { + settings <- c.downField(properties.settings).as[RawRorSettings] + mocks <- c.downField(properties.mocks).as[AuthServicesMocks] + expirationTtl <- c.downField(properties.expirationTtl).as[PositiveFiniteDuration] + expirationTime <- c.downField(properties.expirationTime).as[Instant] + } yield TestRorSettings(settings, mocks, Expiration(expirationTtl, expirationTime)) + } + + private implicit def settingsCodec(implicit yamlParser: RawRorSettingsYamlParser): Codec[RawRorSettings] = + new RawRorSettingsCodec(yamlParser) + + private implicit val mocksCodec: Codec[AuthServicesMocks] = { + implicit val nonEmptyStringCodec: Codec[NonEmptyString] = + Codec.from(Decoder.decodeString.emap(NonEmptyString.from), Encoder.encodeString.contramap(_.value)) + implicit val userIdCodec: Codec[User.Id] = + Codec.from(nonEmptyStringCodec.map(User.Id.apply), nonEmptyStringCodec.contramap(_.value)) + implicit val groupIdCodec: Codec[GroupId] = + Codec.from( + nonEmptyStringCodec.map(GroupId.apply), + nonEmptyStringCodec.contramap(_.value) + ) + + implicit val groupCodec: Codec[Group] = { + implicit val groupNameCodec: Codec[GroupName] = Codec.from( + nonEmptyStringCodec.map(GroupName.apply), + nonEmptyStringCodec.contramap(_.value) + ) + Codec.forProduct2("id", "name")(Group.apply)(group => (group.id, group.name)) + } + implicit val ldapServiceMock: Codec[LdapServiceMock] = { + implicit val userMock: Codec[LdapUserMock] = { + // "groups" left for backward compatibility + val encoder: Encoder[LdapUserMock] = Encoder.forProduct3("id", "groups", "userGroups")( + userMock => (userMock.id, userMock.groups.map(_.id), userMock.groups) + ) + val deprecatedFormatDecoder = Decoder.forProduct2("id", "groups")((id: User.Id, groupIds: List[GroupId]) => + LdapUserMock(id, groupIds.map(Group.from).toCovariantSet) + ) + val decoder: Decoder[LdapUserMock] = Decoder.forProduct2("id", "userGroups")(LdapUserMock.apply) + Codec.from(decoder.or(deprecatedFormatDecoder), encoder) + } + Codec.forProduct1("users")(LdapServiceMock.apply)(_.users) + } + + implicit val extAuthenticationMock: Codec[ExternalAuthenticationServiceMock] = { + implicit val userMock: Codec[ExternalAuthenticationUserMock] = + Codec.forProduct1("id")(ExternalAuthenticationUserMock.apply)(_.id) + Codec.forProduct1("users")(ExternalAuthenticationServiceMock.apply)(_.users) + } + + implicit val extAuthorizationMock: Codec[ExternalAuthorizationServiceMock] = { + implicit val userMock: Codec[ExternalAuthorizationServiceUserMock] = { + // "groups" left for backward compatibility + val encoder = Encoder.forProduct3("id", "groups", "userGroups")( + (userMock: ExternalAuthorizationServiceUserMock) => (userMock.id, userMock.groups.map(_.id), userMock.groups) + ) + val deprecatedFormatDecoder = Decoder.forProduct2("id", "groups")((id: User.Id, groupIds: List[GroupId]) => + ExternalAuthorizationServiceUserMock(id, groupIds.map(Group.from).toCovariantSet) + ) + val decoder = Decoder.forProduct2("id", "userGroups")(ExternalAuthorizationServiceUserMock.apply) + Codec.from(decoder.or(deprecatedFormatDecoder), encoder) + } + Codec.forProduct1("users")(ExternalAuthorizationServiceMock.apply)(_.users) + } + + implicit val ldapKeyCodec: KeyCodec[LdapService.Name] = KeyCodec.from[LdapService.Name]( + NonEmptyString.unapply(_).map(LdapService.Name.apply), + _.value.value + ) + + implicit val externalAuthenticationKeyCodec: KeyCodec[ExternalAuthenticationService.Name] = + KeyCodec.from[ExternalAuthenticationService.Name]( + NonEmptyString.unapply(_).map(ExternalAuthenticationService.Name.apply), + _.value.value + ) + + implicit val externalAuthorizationKeyCodec: KeyCodec[ExternalAuthorizationService.Name] = + KeyCodec.from[ExternalAuthorizationService.Name]( + NonEmptyString.unapply(_).map(ExternalAuthorizationService.Name.apply), + _.value.value + ) + + Codec.forProduct3( + "ldapMocks", + "externalAuthenticationMocks", + "externalAuthorizationMocks" + )(AuthServicesMocks.apply)(e => (e.ldapMocks, e.externalAuthenticationServiceMocks, e.externalAuthorizationServiceMocks)) + } + + private implicit lazy val expirationTtlDecoder: Codec[PositiveFiniteDuration] = { + val decoder = Decoder.decodeString.emap { str => + for { + duration <- Try(Duration(str.toLong, "ms")).toEither.left.map(_ => s"Cannot create decode string '$str' to duration") + positiveFiniteDuration <- duration.toRefinedPositive + } yield positiveFiniteDuration + } + val encoder: Encoder[PositiveFiniteDuration] = Encoder.encodeString.contramap(_.value.toMillis.toString) + Codec.from(decoder, encoder) + } + + private implicit lazy val expirationTimeDecoder: Decoder[Instant] = { + val decoder = Decoder.decodeString.emap { str => + Try(DateTimeFormatter.ISO_DATE_TIME.parse(str)) + .map(Instant.from) + .toEither + .left.map(_ => s"Cannot decode string 'str' to date") + } + val encoder: Encoder[Instant] = Encoder.encodeString.contramap(_.atOffset(ZoneOffset.UTC).toString) + Codec.from(decoder, encoder) + } + +} \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/utils/RefinedUtils.scala b/core/src/main/scala/tech/beshu/ror/utils/RefinedUtils.scala index 95d310048a..43df378c75 100644 --- a/core/src/main/scala/tech/beshu/ror/utils/RefinedUtils.scala +++ b/core/src/main/scala/tech/beshu/ror/utils/RefinedUtils.scala @@ -22,8 +22,8 @@ import eu.timepit.refined.api.Refined import eu.timepit.refined.numeric.Positive import eu.timepit.refined.types.string.NonEmptyString import io.circe.Decoder -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.* -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.ValueLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.* +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.ValueLevelCreationError import tech.beshu.ror.accesscontrol.utils.SyncDecoderCreator import scala.compiletime.error diff --git a/core/src/main/scala/tech/beshu/ror/utils/SSLCertHelper.scala b/core/src/main/scala/tech/beshu/ror/utils/SSLCertHelper.scala index 9abf13d1a7..b6e07c5180 100644 --- a/core/src/main/scala/tech/beshu/ror/utils/SSLCertHelper.scala +++ b/core/src/main/scala/tech/beshu/ror/utils/SSLCertHelper.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.utils +import better.files.* import cats.effect.{IO, Resource} import io.netty.buffer.ByteBufAllocator import io.netty.channel.ChannelHandlerContext @@ -26,13 +27,14 @@ import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider import org.bouncycastle.openssl.PEMParser import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter -import tech.beshu.ror.configuration.SslConfiguration -import tech.beshu.ror.configuration.SslConfiguration.* -import tech.beshu.ror.configuration.SslConfiguration.ClientCertificateConfiguration.TruststoreBasedConfiguration -import tech.beshu.ror.configuration.SslConfiguration.ServerCertificateConfiguration.KeystoreBasedConfiguration +import tech.beshu.ror.settings.es.SslSettings +import tech.beshu.ror.settings.es.SslSettings.* +import tech.beshu.ror.settings.es.SslSettings.ClientCertificateSettings.TruststoreBasedSettings +import tech.beshu.ror.settings.es.SslSettings.ServerCertificateSettings.KeystoreBasedSettings import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant -import java.io.{File, FileInputStream, FileReader, IOException} +import java.io.{FileInputStream, FileReader, IOException} import java.security.cert.{CertificateFactory, X509Certificate} import java.security.{KeyStore, PrivateKey} import javax.net.ssl.{KeyManagerFactory, SNIServerName, SSLEngine, TrustManagerFactory} @@ -64,38 +66,36 @@ object SSLCertHelper extends Logging { sslEngine } - def prepareClientSSLContext(sslConfiguration: SslConfiguration, - fipsCompliant: Boolean, - certificateVerificationEnabled: Boolean): SslContext = { + def prepareClientSSLContext(sslSettings: SslSettings): SslContext = { val builder = - if (certificateVerificationEnabled) { - sslConfiguration.clientCertificateConfiguration match { - case Some(truststoreBasedConfiguration: TruststoreBasedConfiguration) => - SslContextBuilder.forClient.trustManager(getTrustManagerFactory(truststoreBasedConfiguration, fipsCompliant)) - case Some(fileBasedConfiguration: ClientCertificateConfiguration.FileBasedConfiguration) => - SslContextBuilder.forClient.trustManager(getTrustedCertificatesFromPemFile(fileBasedConfiguration).toList.asJava) + if (sslSettings.certificateVerificationEnabled) { + sslSettings.clientCertificateSettings match { + case Some(truststoreBasedSettings: TruststoreBasedSettings) => + SslContextBuilder.forClient.trustManager(getTrustManagerFactory(truststoreBasedSettings, sslSettings.fipsMode.isSslFipsCompliant)) + case Some(fileBasedSettings: ClientCertificateSettings.FileBasedSettings) => + SslContextBuilder.forClient.trustManager(getTrustedCertificatesFromPemFile(fileBasedSettings).toList.asJava) case None => throw new Exception("Client Authentication could not be enabled because trust certificates has not been configured") } } else { SslContextBuilder.forClient.trustManager(InsecureTrustManagerFactory.INSTANCE) } - val result = if (fipsCompliant) { - val keystoreBasedConfiguration = sslConfiguration.serverCertificateConfiguration match { - case keystoreBasedConfiguration: KeystoreBasedConfiguration => keystoreBasedConfiguration - case _ => throw new Exception("KeyStore based configuration is required in FIPS compliant mode") + val result = if (sslSettings.fipsMode.isSslFipsCompliant) { + val keystoreBasedSettings = sslSettings.serverCertificateSettings match { + case keystoreBasedSettings: KeystoreBasedSettings => keystoreBasedSettings + case _ => throw new Exception("KeyStore based settings is required in FIPS compliant mode") } - getFipsCompliantKeyManagerFactory(keystoreBasedConfiguration) + getFipsCompliantKeyManagerFactory(keystoreBasedSettings) .map { keyManagerFactory => logger.info(s"Initializing ROR SSL using SSL provider: ${keyManagerFactory.getProvider.getName.show}") builder.keyManager(keyManagerFactory) } } else { - (sslConfiguration.serverCertificateConfiguration match { - case fileBasedConfiguration: ServerCertificateConfiguration.FileBasedConfiguration => - getPrivateKeyAndCertificateChainFromPemFiles(fileBasedConfiguration) - case keystoreBasedConfiguration: KeystoreBasedConfiguration => - getPrivateKeyAndCertificateChainFromKeystore(keystoreBasedConfiguration) + (sslSettings.serverCertificateSettings match { + case fileBasedSettings: ServerCertificateSettings.FileBasedSettings => + getPrivateKeyAndCertificateChainFromPemFiles(fileBasedSettings) + case keystoreBasedSettings: KeystoreBasedSettings => + getPrivateKeyAndCertificateChainFromKeystore(keystoreBasedSettings) }).map { case (privateKey, certificateChain) => logger.info(s"Initializing ROR SSL using default SSL provider ${SslContext.defaultServerProvider().name().show}") builder.keyManager(privateKey, certificateChain.toList.asJava) @@ -104,30 +104,28 @@ object SSLCertHelper extends Logging { result.unsafeRunSync().build() } - def prepareServerSSLContext(sslConfiguration: SslConfiguration, - fipsCompliant: Boolean, - clientAuthenticationEnabled: Boolean): SslContext = { - prepareSslContextBuilder(sslConfiguration, fipsCompliant) + def prepareServerSSLContext(sslSettings: SslSettings, clientAuthenticationEnabled: Boolean): SslContext = { + prepareSslContextBuilder(sslSettings) .attempt .map { case Right(sslCtxBuilder) => - areProtocolAndCiphersValid(sslCtxBuilder, sslConfiguration) - if (sslConfiguration.allowedCiphers.nonEmpty) { - sslCtxBuilder.ciphers(sslConfiguration.allowedCiphers.map(_.value).asJava) + areProtocolAndCiphersValid(sslCtxBuilder, sslSettings) + if (sslSettings.allowedCiphers.nonEmpty) { + sslCtxBuilder.ciphers(sslSettings.allowedCiphers.map(_.value).asJava) } if (clientAuthenticationEnabled) { sslCtxBuilder.clientAuth(ClientAuth.REQUIRE) - sslConfiguration.clientCertificateConfiguration match { - case Some(truststoreBasedConfiguration: TruststoreBasedConfiguration) => - sslCtxBuilder.trustManager(getTrustManagerFactory(truststoreBasedConfiguration, fipsCompliant)) - case Some(fileBasedConfiguration: ClientCertificateConfiguration.FileBasedConfiguration) => - sslCtxBuilder.trustManager(getTrustedCertificatesFromPemFile(fileBasedConfiguration).toList.asJava) + sslSettings.clientCertificateSettings match { + case Some(truststoreBasedSettings: TruststoreBasedSettings) => + sslCtxBuilder.trustManager(getTrustManagerFactory(truststoreBasedSettings, sslSettings.fipsMode.isSslFipsCompliant)) + case Some(fileBasedSettings: ClientCertificateSettings.FileBasedSettings) => + sslCtxBuilder.trustManager(getTrustedCertificatesFromPemFile(fileBasedSettings).toList.asJava) case None => throw new Exception("Client Authentication could not be enabled because trust certificates has not been configured") } } - if (sslConfiguration.allowedProtocols.nonEmpty) { - sslCtxBuilder.protocols(sslConfiguration.allowedProtocols.map(_.value).asJava) + if (sslSettings.allowedProtocols.nonEmpty) { + sslCtxBuilder.protocols(sslSettings.allowedProtocols.map(_.value).asJava) } sslCtxBuilder.build() case Left(exception: IOException) => @@ -145,8 +143,8 @@ object SSLCertHelper extends Logging { .isSuccess } - def getTrustedCertificatesFromPemFile(fileBasedConfiguration: ClientCertificateConfiguration.FileBasedConfiguration): Array[X509Certificate] = { - loadCertificateChain(fileBasedConfiguration.clientTrustedCertificateFile.value) + private def getTrustedCertificatesFromPemFile(fileBasedSettings: ClientCertificateSettings.FileBasedSettings): Array[X509Certificate] = { + loadCertificateChain(fileBasedSettings.clientTrustedCertificateFile.value) .attempt .map { case Right(certificateChain) => certificateChain @@ -156,8 +154,8 @@ object SSLCertHelper extends Logging { .unsafeRunSync() } - def getTrustManagerFactory(truststoreBasedConfiguration: TruststoreBasedConfiguration, fipsCompliant: Boolean): TrustManagerFactory = { - loadTruststore(truststoreBasedConfiguration, fipsCompliant) + private def getTrustManagerFactory(truststoreBasedSettings: TruststoreBasedSettings, fipsCompliant: Boolean): TrustManagerFactory = { + loadTruststore(truststoreBasedSettings, fipsCompliant) .map { truststore => val tmf = getTrustManagerFactoryInstance(fipsCompliant) tmf.init(truststore) @@ -173,8 +171,8 @@ object SSLCertHelper extends Logging { } private def areProtocolAndCiphersValid(sslContextBuilder: SslContextBuilder, - config: SslConfiguration): Boolean = - trySetProtocolsAndCiphersInsideNewEngine(sslContextBuilder: SslContextBuilder, config) + sslSettings: SslSettings): Boolean = + trySetProtocolsAndCiphersInsideNewEngine(sslContextBuilder: SslContextBuilder, sslSettings) .fold( ex => { logger.error(s"ROR SSL: cannot validate SSL protocols and ciphers! ${ex.getClass.getSimpleName.show} : ${ex.getMessage.show}", ex) @@ -201,7 +199,7 @@ object SSLCertHelper extends Logging { private def loadKeystoreFromFile(keystoreFile: File, password: Array[Char], fipsCompliant: Boolean): IO[KeyStore] = { Resource - .fromAutoCloseable(IO(new FileInputStream(keystoreFile))) + .fromAutoCloseable(IO(new FileInputStream(keystoreFile.toJava))) .use { keystoreFile => IO { val keystore = if (fipsCompliant) { @@ -217,22 +215,22 @@ object SSLCertHelper extends Logging { } } - private def loadKeystore(keystoreBasedConfiguration: KeystoreBasedConfiguration, fipsCompliant: Boolean): IO[KeyStore] = { + private def loadKeystore(keystoreBasedSettings: KeystoreBasedSettings, fipsCompliant: Boolean): IO[KeyStore] = { for { _ <- IO(logger.info("Preparing keystore...")) - keystore <- loadKeystoreFromFile(keystoreBasedConfiguration.keystoreFile.value, keystoreBasedConfiguration.keystorePassword, fipsCompliant) + keystore <- loadKeystoreFromFile(keystoreBasedSettings.keystoreFile.value, keystoreBasedSettings.keystorePassword, fipsCompliant) } yield keystore } - private def loadTruststore(truststoreBasedConfiguration: TruststoreBasedConfiguration, fipsCompliant: Boolean): IO[KeyStore] = { + private def loadTruststore(truststoreBasedSettings: TruststoreBasedSettings, fipsCompliant: Boolean): IO[KeyStore] = { for { _ <- IO(logger.info("Preparing truststore...")) - truststore <- loadKeystoreFromFile(truststoreBasedConfiguration.truststoreFile.value, truststoreBasedConfiguration.truststorePassword, fipsCompliant) + truststore <- loadKeystoreFromFile(truststoreBasedSettings.truststoreFile.value, truststoreBasedSettings.truststorePassword, fipsCompliant) } yield truststore } - private def prepareAlias(keystore: KeyStore, config: KeystoreBasedConfiguration) = - config.keyAlias match { + private def prepareAlias(keystore: KeyStore, settings: KeystoreBasedSettings) = + settings.keyAlias match { case None if keystore.aliases().hasMoreElements => val firstAlias = keystore.aliases().nextElement() logger.info(s"ROR SSL: ssl.key_alias not configured, took first alias in keystore: ${firstAlias.show}") @@ -248,29 +246,29 @@ object SSLCertHelper extends Logging { unnecessaryAliases.foreach(keystore.deleteEntry) } - private def getFipsCompliantKeyManagerFactory(keystoreBasedConfiguration: KeystoreBasedConfiguration): IO[KeyManagerFactory] = { - loadKeystore(keystoreBasedConfiguration, fipsCompliant = true) + private def getFipsCompliantKeyManagerFactory(keystoreBasedSettings: KeystoreBasedSettings): IO[KeyManagerFactory] = { + loadKeystore(keystoreBasedSettings, fipsCompliant = true) .map { keystore => - if (keystoreBasedConfiguration.keyPass.isDefined) { - logger.warn("ROR configuration parameter key_pass is declared however it won't be used in this mode. In this case password for specific key MUST be the same as keystore password") + if (keystoreBasedSettings.keyPass.isDefined) { + logger.warn("ROR settings parameter key_pass is declared however it won't be used in this mode. In this case password for specific key MUST be the same as keystore password") } - removeAllAliasesFromKeystoreBesidesOne(keystore, prepareAlias(keystore, keystoreBasedConfiguration)) + removeAllAliasesFromKeystoreBesidesOne(keystore, prepareAlias(keystore, keystoreBasedSettings)) val kmf = getKeyManagerFactoryInstance(fipsCompliant = true) - kmf.init(keystore, keystoreBasedConfiguration.keystorePassword) + kmf.init(keystore, keystoreBasedSettings.keystorePassword) kmf } } - private def getPrivateKeyAndCertificateChainFromPemFiles(fileBasedConfiguration: ServerCertificateConfiguration.FileBasedConfiguration): IO[(PrivateKey, Array[X509Certificate])] = { + private def getPrivateKeyAndCertificateChainFromPemFiles(fileBasedSettings: ServerCertificateSettings.FileBasedSettings): IO[(PrivateKey, Array[X509Certificate])] = { for { - privateKey <- loadPrivateKey(fileBasedConfiguration.serverCertificateKeyFile.value) - certificateChain <- loadCertificateChain(fileBasedConfiguration.serverCertificateFile.value) + privateKey <- loadPrivateKey(fileBasedSettings.serverCertificateKeyFile.value) + certificateChain <- loadCertificateChain(fileBasedSettings.serverCertificateFile.value) } yield (privateKey, certificateChain) } private def loadPrivateKey(file: File): IO[PrivateKey] = { Resource - .fromAutoCloseable(IO(new FileReader(file))) + .fromAutoCloseable(IO(new FileReader(file.toJava))) .use { privateKeyFileReader => IO { val pemParser = new PEMParser(privateKeyFileReader) @@ -283,7 +281,7 @@ object SSLCertHelper extends Logging { private def loadCertificateChain(file: File): IO[Array[X509Certificate]] = { Resource - .fromAutoCloseable(IO(new FileInputStream(file))) + .fromAutoCloseable(IO(new FileInputStream(file.toJava))) .use { certificateChainFile => IO { val certFactory = CertificateFactory.getInstance("X.509") @@ -295,11 +293,11 @@ object SSLCertHelper extends Logging { } } - private def getPrivateKeyAndCertificateChainFromKeystore(keystoreBasedConfiguration: KeystoreBasedConfiguration): IO[(PrivateKey, Array[X509Certificate])] = { - loadKeystore(keystoreBasedConfiguration, fipsCompliant = false) + private def getPrivateKeyAndCertificateChainFromKeystore(keystoreBasedSettings: KeystoreBasedSettings): IO[(PrivateKey, Array[X509Certificate])] = { + loadKeystore(keystoreBasedSettings, fipsCompliant = false) .map { keystore => - val alias = prepareAlias(keystore, keystoreBasedConfiguration) - val privateKey = keystore.getKey(alias, keystoreBasedConfiguration.keyPass.map(_.value.toCharArray).orNull) match { + val alias = prepareAlias(keystore, keystoreBasedSettings) + val privateKey = keystore.getKey(alias, keystoreBasedSettings.keyPass.map(_.value.toCharArray).orNull) match { case pk: PrivateKey => pk case _ => throw MalformedSslSettings(s"Configured key with alias=${alias.show} is not a private key") } @@ -312,37 +310,37 @@ object SSLCertHelper extends Logging { } private def trySetProtocolsAndCiphersInsideNewEngine(sslContextBuilder: SslContextBuilder, - config: SslConfiguration) = Try { + sslSettings: SslSettings) = Try { val sslEngine = sslContextBuilder.build().newEngine(ByteBufAllocator.DEFAULT) logger.info(s"ROR SSL: Available ciphers: ${sslEngine.getEnabledCipherSuites.toList.show}") - if (config.allowedCiphers.nonEmpty) { - sslEngine.setEnabledCipherSuites(config.allowedCiphers.map(_.value).toArray) + if (sslSettings.allowedCiphers.nonEmpty) { + sslEngine.setEnabledCipherSuites(sslSettings.allowedCiphers.map(_.value).toArray) logger.info(s"ROR SSL: Restricting to ciphers: ${sslEngine.getEnabledCipherSuites.toList.show}") } logger.info(s"ROR SSL: Available SSL protocols: ${sslEngine.getEnabledProtocols.toList.show}") - if (config.allowedProtocols.nonEmpty) { - sslEngine.setEnabledProtocols(config.allowedProtocols.map(_.value).toArray) + if (sslSettings.allowedProtocols.nonEmpty) { + sslEngine.setEnabledProtocols(sslSettings.allowedProtocols.map(_.value).toArray) logger.info(s"ROR SSL: Restricting to SSL protocols: ${sslEngine.getEnabledProtocols.toList.show}") } } - private def prepareSslContextBuilder(sslConfiguration: SslConfiguration, fipsCompliant: Boolean): IO[SslContextBuilder] = { - if (fipsCompliant) { - val keystoreBasedConfiguration = sslConfiguration.serverCertificateConfiguration match { - case keystoreBasedConfiguration: KeystoreBasedConfiguration => keystoreBasedConfiguration - case _ => throw new Exception("KeyStore based configuration is required in FIPS compliant mode") + private def prepareSslContextBuilder(sslSettings: SslSettings): IO[SslContextBuilder] = { + if (sslSettings.fipsMode.isSslFipsCompliant) { + val keystoreBasedSettings = sslSettings.serverCertificateSettings match { + case keystoreBasedSettings: KeystoreBasedSettings => keystoreBasedSettings + case _ => throw new Exception("KeyStore based settings is required in FIPS compliant mode") } - getFipsCompliantKeyManagerFactory(keystoreBasedConfiguration) + getFipsCompliantKeyManagerFactory(keystoreBasedSettings) .map { keyManagerFactory => logger.info(s"Initializing ROR SSL using SSL provider: ${keyManagerFactory.getProvider.getName.show}") SslContextBuilder.forServer(keyManagerFactory) } } else { - (sslConfiguration.serverCertificateConfiguration match { - case fileBasedConfiguration: ServerCertificateConfiguration.FileBasedConfiguration => - getPrivateKeyAndCertificateChainFromPemFiles(fileBasedConfiguration) - case keystoreBasedConfiguration: KeystoreBasedConfiguration => - getPrivateKeyAndCertificateChainFromKeystore(keystoreBasedConfiguration) + (sslSettings.serverCertificateSettings match { + case fileBasedSettings: ServerCertificateSettings.FileBasedSettings => + getPrivateKeyAndCertificateChainFromPemFiles(fileBasedSettings) + case keystoreBasedSettings: KeystoreBasedSettings => + getPrivateKeyAndCertificateChainFromKeystore(keystoreBasedSettings) }).map { case (privateKey, certificateChain) => logger.info(s"Initializing ROR SSL using default SSL provider ${SslContext.defaultServerProvider().name().show}") SslContextBuilder.forServer(privateKey, certificateChain.toList.asJava) diff --git a/core/src/main/scala/tech/beshu/ror/utils/ScalaOps.scala b/core/src/main/scala/tech/beshu/ror/utils/ScalaOps.scala index 5aa99ce6f7..f038a469a3 100644 --- a/core/src/main/scala/tech/beshu/ror/utils/ScalaOps.scala +++ b/core/src/main/scala/tech/beshu/ror/utils/ScalaOps.scala @@ -24,10 +24,10 @@ import eu.timepit.refined.api.Refined import eu.timepit.refined.types.string.NonEmptyString import monix.eval.Task import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logger import tech.beshu.ror.syntax.* import tech.beshu.ror.utils.DurationOps.PositiveFiniteDuration -import java.util import java.util.Base64 import scala.collection.immutable.SortedSet import scala.concurrent.duration.* @@ -243,4 +243,26 @@ object ScalaOps { Refined.unsafeApply(this.duration.value + duration.value) } } + + implicit class LoggerOps(logger: Logger) { + def dInfo(msg: String): Task[Unit] = { + Task.delay(logger.info(msg)) + } + + def dWarn(msg: String): Task[Unit] = { + Task.delay(logger.warn(msg)) + } + + def dDebug(msg: String): Task[Unit] = { + Task.delay(logger.debug(msg)) + } + + def dError(msg: String): Task[Unit] = { + Task.delay(logger.error(msg)) + } + } + + implicit class EitherTOps(t: EitherT.type) extends AnyVal { + def liftTask[A](value: => A): EitherT[Task, Nothing, A] = EitherT(Task.delay(Right(value))) + } } diff --git a/core/src/main/scala/tech/beshu/ror/utils/yaml/RorYamlParser.scala b/core/src/main/scala/tech/beshu/ror/utils/yaml/RorYamlParser.scala deleted file mode 100644 index 78d6949321..0000000000 --- a/core/src/main/scala/tech/beshu/ror/utils/yaml/RorYamlParser.scala +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.utils.yaml - -import better.files.* -import io.circe.* -import squants.information.Information -import tech.beshu.ror.org.yaml.snakeyaml.LoaderOptions - -import java.io.{Reader, StringReader} - -class RorYamlParser(maxSize: Information) { - - def parse(yaml: Reader): Either[ParsingFailure, Json] = { - tech.beshu.ror.utils.yaml.parser.parse(yaml, loaderOptions) - } - - def parse(file: File): Either[ParsingFailure, Json] = { - file.fileReader { reader => parse(reader) } - } - - def parse(yamlContent: String): Either[ParsingFailure, Json] = { - parse(new StringReader(yamlContent)) - } - - private lazy val loaderOptions: LoaderOptions = { - val options = new LoaderOptions - options.setCodePointLimit(maxSize.toBytes.toInt) - options - } -} diff --git a/core/src/main/scala/tech/beshu/ror/utils/yaml/YamlKeyDecoder.scala b/core/src/main/scala/tech/beshu/ror/utils/yaml/YamlKeyDecoder.scala index b12c503c4a..6d6adc59d4 100644 --- a/core/src/main/scala/tech/beshu/ror/utils/yaml/YamlKeyDecoder.scala +++ b/core/src/main/scala/tech/beshu/ror/utils/yaml/YamlKeyDecoder.scala @@ -20,15 +20,13 @@ import cats.data.NonEmptyList import io.circe.Decoder.Result import io.circe.{ACursor, Decoder, HCursor} -private class YamlKeyDecoder[A: Decoder](segments: NonEmptyList[String], default: A) extends Decoder[A] { - override def apply(c: HCursor): Result[A] = { +private class YamlKeyDecoder[A: Decoder](segments: NonEmptyList[String]) extends Decoder[Option[A]] { + override def apply(c: HCursor): Result[Option[A]] = { for { oneLine <- downOneLineField(c).as[Option[A]] multiLine <- downMultiLineField(c).as[Option[A]] } yield { - oneLine - .orElse(multiLine) - .getOrElse(default) + oneLine.orElse(multiLine) } } @@ -44,7 +42,21 @@ private class YamlKeyDecoder[A: Decoder](segments: NonEmptyList[String], default } object YamlKeyDecoder { - def apply[A: Decoder](segments: NonEmptyList[String], default: A): Decoder[A] = { - new YamlKeyDecoder[A](segments, default) + def apply[A: Decoder](path: NonEmptyList[String], default: A): Decoder[A] = { + apply(path).map(_.getOrElse(default)) + } + + def apply[A: Decoder](path: NonEmptyList[String], alternativePath: NonEmptyList[String], default: A): Decoder[A] = { + for { + decodedValue <- apply(path) + alternativeDecodedValue <- decodedValue match { + case Some(value) => Decoder.const[Option[A]](Some(value)) + case None => apply(alternativePath) + } + } yield alternativeDecodedValue.getOrElse(default) + } + + def apply[A: Decoder](path: NonEmptyList[String]): Decoder[Option[A]] = { + new YamlKeyDecoder[A](path) } } \ No newline at end of file diff --git a/core/src/main/scala/tech/beshu/ror/utils/yaml/YamlParser.scala b/core/src/main/scala/tech/beshu/ror/utils/yaml/YamlParser.scala new file mode 100644 index 0000000000..5421b384b6 --- /dev/null +++ b/core/src/main/scala/tech/beshu/ror/utils/yaml/YamlParser.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.utils.yaml + +import better.files.* +import io.circe.* +import squants.information.Information +import tech.beshu.ror.org.yaml.snakeyaml.LoaderOptions + +import java.io.{Reader, StringReader} + +class YamlParser(maxSize: Option[Information] = None) { + + def parse(yaml: Reader): Either[ParsingFailure, Json] = { + tech.beshu.ror.utils.yaml.parser.parse(yaml, loaderOptions) + } + + def parse(file: File): Either[ParsingFailure, Json] = { + file.fileReader { reader => parse(reader) } + } + + def parse(yamlContent: String): Either[ParsingFailure, Json] = { + parse(new StringReader(yamlContent)) + } + + private lazy val loaderOptions: LoaderOptions = { + val options = new LoaderOptions + maxSize.foreach { m => options.setCodePointLimit(m.toBytes.toInt) } + options + } +} diff --git a/core/src/test/resources/boot_tests/bad_index_config/readonlyrest_index.yml b/core/src/test/resources/boot_tests/bad_index_config/readonlyrest_index.yml deleted file mode 100644 index 1ed9fb2b66..0000000000 --- a/core/src/test/resources/boot_tests/bad_index_config/readonlyrest_index.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - unknown_rule: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/bad_index_config/elasticsearch.yml b/core/src/test/resources/boot_tests/bad_index_settings/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/bad_index_config/elasticsearch.yml rename to core/src/test/resources/boot_tests/bad_index_settings/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/bad_index_settings/readonlyrest_index.yml b/core/src/test/resources/boot_tests/bad_index_settings/readonlyrest_index.yml new file mode 100644 index 0000000000..3cc437d0a8 --- /dev/null +++ b/core/src/test/resources/boot_tests/bad_index_settings/readonlyrest_index.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + unknown_rule: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/config_reloading/readonlyrest_first.yml b/core/src/test/resources/boot_tests/config_reloading/readonlyrest_first.yml deleted file mode 100644 index 728b72e827..0000000000 --- a/core/src/test/resources/boot_tests/config_reloading/readonlyrest_first.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "INDEX MODIFIED: CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/config_reloading/readonlyrest_initial.yml b/core/src/test/resources/boot_tests/config_reloading/readonlyrest_initial.yml deleted file mode 100644 index 20de7615cc..0000000000 --- a/core/src/test/resources/boot_tests/config_reloading/readonlyrest_initial.yml +++ /dev/null @@ -1,12 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "INDEX MODIFIED: CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container - - - name: "User 1" - auth_key: user1:pass \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/config_reloading/readonlyrest_second.yml b/core/src/test/resources/boot_tests/config_reloading/readonlyrest_second.yml deleted file mode 100644 index 25896ce915..0000000000 --- a/core/src/test/resources/boot_tests/config_reloading/readonlyrest_second.yml +++ /dev/null @@ -1,12 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "INDEX MODIFIED: CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container - - - name: "User 2" - auth_key: user2:pass \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_both_pem_and_keystore_configured/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_both_pem_and_keystore_configured/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/es_api_ssl_settings_both_pem_and_keystore_configured/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_disabled/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_disabled/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/es_api_ssl_settings_disabled/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_file_invalid_yaml/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_file_invalid_yaml/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/es_api_ssl_settings_file_invalid_yaml/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_in_elasticsearch_config/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_in_elasticsearch_config/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/es_api_ssl_settings_in_elasticsearch_config/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_in_elasticsearch_config_only_digits/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_in_elasticsearch_config_only_digits/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/es_api_ssl_settings_in_elasticsearch_config_only_digits/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_config/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_config/readonlyrest.yml deleted file mode 100644 index e31469bb3f..0000000000 --- a/core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_config/readonlyrest.yml +++ /dev/null @@ -1,17 +0,0 @@ -readonlyrest: - - ssl: - enable: true - keystore_file: "ror-keystore.jks" - keystore_pass: readonlyrest1 - key_pass: readonlyrest2 - truststore_file: "ror-truststore.jks" - truststore_pass: readonlyrest3 - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_config/elasticsearch.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_settings/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_config/elasticsearch.yml rename to core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_settings/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_settings/readonlyrest.yml new file mode 100644 index 0000000000..dcdb50d7c7 --- /dev/null +++ b/core/src/test/resources/boot_tests/es_api_ssl_settings_in_readonlyrest_settings/readonlyrest.yml @@ -0,0 +1,13 @@ +readonlyrest: + + ssl: + enable: true + keystore_file: "ror-keystore.jks" + keystore_pass: readonlyrest1 + key_pass: readonlyrest2 + truststore_file: "ror-truststore.jks" + truststore_pass: readonlyrest3 + + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_malformed/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_malformed/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/es_api_ssl_settings_malformed/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/es_api_ssl_settings_pem_files/readonlyrest.yml b/core/src/test/resources/boot_tests/es_api_ssl_settings_pem_files/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/es_api_ssl_settings_pem_files/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/forced_file_loading/readonlyrest.yml b/core/src/test/resources/boot_tests/forced_file_loading/readonlyrest.yml index 52df94de84..de26466d1e 100644 --- a/core/src/test/resources/boot_tests/forced_file_loading/readonlyrest.yml +++ b/core/src/test/resources/boot_tests/forced_file_loading/readonlyrest.yml @@ -1,9 +1,4 @@ readonlyrest: - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/forced_file_loading_bad_config/readonlyrest.yml b/core/src/test/resources/boot_tests/forced_file_loading_bad_config/readonlyrest.yml deleted file mode 100644 index 1ed9fb2b66..0000000000 --- a/core/src/test/resources/boot_tests/forced_file_loading_bad_config/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - unknown_rule: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/forced_file_loading_bad_config/elasticsearch.yml b/core/src/test/resources/boot_tests/forced_file_loading_bad_settings/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/forced_file_loading_bad_config/elasticsearch.yml rename to core/src/test/resources/boot_tests/forced_file_loading_bad_settings/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/forced_file_loading_bad_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/forced_file_loading_bad_settings/readonlyrest.yml new file mode 100644 index 0000000000..a68f8684ea --- /dev/null +++ b/core/src/test/resources/boot_tests/forced_file_loading_bad_settings/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + unknown_rule: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/forced_file_loading_malformed_config/readonlyrest.yml b/core/src/test/resources/boot_tests/forced_file_loading_malformed_config/readonlyrest.yml deleted file mode 100644 index 78af795426..0000000000 --- a/core/src/test/resources/boot_tests/forced_file_loading_malformed_config/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: "admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/forced_file_loading_malformed_config/elasticsearch.yml b/core/src/test/resources/boot_tests/forced_file_loading_malformed_settings/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/forced_file_loading_malformed_config/elasticsearch.yml rename to core/src/test/resources/boot_tests/forced_file_loading_malformed_settings/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/forced_file_loading_malformed_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/forced_file_loading_malformed_settings/readonlyrest.yml new file mode 100644 index 0000000000..1577c48520 --- /dev/null +++ b/core/src/test/resources/boot_tests/forced_file_loading_malformed_settings/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: "admin:admin # wrong YAML \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/forced_file_loading_with_audit/readonlyrest.yml b/core/src/test/resources/boot_tests/forced_file_loading_with_audit/readonlyrest.yml index 58d8e43093..c5c6786aed 100644 --- a/core/src/test/resources/boot_tests/forced_file_loading_with_audit/readonlyrest.yml +++ b/core/src/test/resources/boot_tests/forced_file_loading_with_audit/readonlyrest.yml @@ -5,9 +5,5 @@ readonlyrest: outputs: [ index, data_stream ] access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - aut h_key: admin:container \ No newline at end of file + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config/custom_index_defined/readonlyrest.yml b/core/src/test/resources/boot_tests/index_config/custom_index_defined/readonlyrest.yml deleted file mode 100644 index 52df94de84..0000000000 --- a/core/src/test/resources/boot_tests/index_config/custom_index_defined/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config/no_index_defined/readonlyrest.yml b/core/src/test/resources/boot_tests/index_config/no_index_defined/readonlyrest.yml deleted file mode 100644 index 52df94de84..0000000000 --- a/core/src/test/resources/boot_tests/index_config/no_index_defined/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_available_file_config_not_provided/readonlyrest_index.yml b/core/src/test/resources/boot_tests/index_config_available_file_config_not_provided/readonlyrest_index.yml deleted file mode 100644 index 728b72e827..0000000000 --- a/core/src/test/resources/boot_tests/index_config_available_file_config_not_provided/readonlyrest_index.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "INDEX MODIFIED: CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_available_file_config_provided/readonlyrest.yml b/core/src/test/resources/boot_tests/index_config_available_file_config_provided/readonlyrest.yml deleted file mode 100644 index 52df94de84..0000000000 --- a/core/src/test/resources/boot_tests/index_config_available_file_config_provided/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_available_file_config_provided/readonlyrest_index.yml b/core/src/test/resources/boot_tests/index_config_available_file_config_provided/readonlyrest_index.yml deleted file mode 100644 index 728b72e827..0000000000 --- a/core/src/test/resources/boot_tests/index_config_available_file_config_provided/readonlyrest_index.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "INDEX MODIFIED: CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_not_exists_bad_file_config/readonlyrest.yml b/core/src/test/resources/boot_tests/index_config_not_exists_bad_file_config/readonlyrest.yml deleted file mode 100644 index 1ed9fb2b66..0000000000 --- a/core/src/test/resources/boot_tests/index_config_not_exists_bad_file_config/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - unknown_rule: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_not_exists_malformed_file_config/readonlyrest.yml b/core/src/test/resources/boot_tests/index_config_not_exists_malformed_file_config/readonlyrest.yml deleted file mode 100644 index 78af795426..0000000000 --- a/core/src/test/resources/boot_tests/index_config_not_exists_malformed_file_config/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: "admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_reloading/readonlyrest.yml b/core/src/test/resources/boot_tests/index_config_reloading/readonlyrest.yml deleted file mode 100644 index 52df94de84..0000000000 --- a/core/src/test/resources/boot_tests/index_config_reloading/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_reloading/updated_readonlyrest.yml b/core/src/test/resources/boot_tests/index_config_reloading/updated_readonlyrest.yml deleted file mode 100644 index e745b695ab..0000000000 --- a/core/src/test/resources/boot_tests/index_config_reloading/updated_readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN - changed" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config/custom_index_defined/elasticsearch.yml b/core/src/test/resources/boot_tests/index_settings/custom_index_defined/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/index_config/custom_index_defined/elasticsearch.yml rename to core/src/test/resources/boot_tests/index_settings/custom_index_defined/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/index_settings/custom_index_defined/readonlyrest.yml b/core/src/test/resources/boot_tests/index_settings/custom_index_defined/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings/custom_index_defined/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/config_reloading/elasticsearch.yml b/core/src/test/resources/boot_tests/index_settings/no_index_defined/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/config_reloading/elasticsearch.yml rename to core/src/test/resources/boot_tests/index_settings/no_index_defined/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/index_settings/no_index_defined/readonlyrest.yml b/core/src/test/resources/boot_tests/index_settings/no_index_defined/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings/no_index_defined/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config/no_index_defined/elasticsearch.yml b/core/src/test/resources/boot_tests/index_settings_available_file_settings_not_provided/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/index_config/no_index_defined/elasticsearch.yml rename to core/src/test/resources/boot_tests/index_settings_available_file_settings_not_provided/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/index_settings_available_file_settings_not_provided/readonlyrest_index.yml b/core/src/test/resources/boot_tests/index_settings_available_file_settings_not_provided/readonlyrest_index.yml new file mode 100644 index 0000000000..2907698ccc --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings_available_file_settings_not_provided/readonlyrest_index.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "INDEX MODIFIED: ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_available_file_config_not_provided/elasticsearch.yml b/core/src/test/resources/boot_tests/index_settings_available_file_settings_provided/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/index_config_available_file_config_not_provided/elasticsearch.yml rename to core/src/test/resources/boot_tests/index_settings_available_file_settings_provided/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/index_settings_available_file_settings_provided/readonlyrest.yml b/core/src/test/resources/boot_tests/index_settings_available_file_settings_provided/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings_available_file_settings_provided/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_settings_available_file_settings_provided/readonlyrest_index.yml b/core/src/test/resources/boot_tests/index_settings_available_file_settings_provided/readonlyrest_index.yml new file mode 100644 index 0000000000..db58e7b79a --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings_available_file_settings_provided/readonlyrest_index.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "INDEX MODIFIED: Admin block" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_available_file_config_provided/elasticsearch.yml b/core/src/test/resources/boot_tests/index_settings_not_exists_bad_file_settings/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/index_config_available_file_config_provided/elasticsearch.yml rename to core/src/test/resources/boot_tests/index_settings_not_exists_bad_file_settings/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/index_settings_not_exists_bad_file_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/index_settings_not_exists_bad_file_settings/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings_not_exists_bad_file_settings/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_not_exists_malformed_file_config/elasticsearch.yml b/core/src/test/resources/boot_tests/index_settings_not_exists_malformed_file_settings/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/index_config_not_exists_malformed_file_config/elasticsearch.yml rename to core/src/test/resources/boot_tests/index_settings_not_exists_malformed_file_settings/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/index_settings_not_exists_malformed_file_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/index_settings_not_exists_malformed_file_settings/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings_not_exists_malformed_file_settings/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_not_exists_bad_file_config/elasticsearch.yml b/core/src/test/resources/boot_tests/index_settings_reloading/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/index_config_not_exists_bad_file_config/elasticsearch.yml rename to core/src/test/resources/boot_tests/index_settings_reloading/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/index_settings_reloading/readonlyrest.yml b/core/src/test/resources/boot_tests/index_settings_reloading/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings_reloading/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_settings_reloading/updated_readonlyrest.yml b/core/src/test/resources/boot_tests/index_settings_reloading/updated_readonlyrest.yml new file mode 100644 index 0000000000..ebf46b3711 --- /dev/null +++ b/core/src/test/resources/boot_tests/index_settings_reloading/updated_readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN - changed" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/internode_ssl_settings_disabled/readonlyrest.yml b/core/src/test/resources/boot_tests/internode_ssl_settings_disabled/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/internode_ssl_settings_disabled/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/internode_ssl_settings_in_elasticsearch_config/readonlyrest.yml b/core/src/test/resources/boot_tests/internode_ssl_settings_in_elasticsearch_config/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/internode_ssl_settings_in_elasticsearch_config/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_config/readonlyrest.yml b/core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_config/readonlyrest.yml deleted file mode 100644 index f9473929b0..0000000000 --- a/core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_config/readonlyrest.yml +++ /dev/null @@ -1,19 +0,0 @@ -readonlyrest: - - ssl_internode: - enable: true - keystore_file: "ror-keystore.jks" - keystore_pass: readonlyrest1 - key_pass: readonlyrest2 - truststore_file: "ror-truststore.jks" - truststore_pass: readonlyrest3 - certificate_verification: true - hostname_verification: true - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_config/elasticsearch.yml b/core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_settings/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_config/elasticsearch.yml rename to core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_settings/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_settings/readonlyrest.yml new file mode 100644 index 0000000000..d8003efdb6 --- /dev/null +++ b/core/src/test/resources/boot_tests/internode_ssl_settings_in_readonlyrest_settings/readonlyrest.yml @@ -0,0 +1,15 @@ +readonlyrest: + + ssl_internode: + enable: true + keystore_file: "ror-keystore.jks" + keystore_pass: readonlyrest1 + key_pass: readonlyrest2 + truststore_file: "ror-truststore.jks" + truststore_pass: readonlyrest3 + certificate_verification: true + hostname_verification: true + + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/internode_ssl_settings_malformed/readonlyrest.yml b/core/src/test/resources/boot_tests/internode_ssl_settings_malformed/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/internode_ssl_settings_malformed/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/internode_ssl_settings_pem_files/readonlyrest.yml b/core/src/test/resources/boot_tests/internode_ssl_settings_pem_files/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/internode_ssl_settings_pem_files/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/malformed_index_config/readonlyrest_index.yml b/core/src/test/resources/boot_tests/malformed_index_config/readonlyrest_index.yml deleted file mode 100644 index 78af795426..0000000000 --- a/core/src/test/resources/boot_tests/malformed_index_config/readonlyrest_index.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: "admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/index_config_reloading/elasticsearch.yml b/core/src/test/resources/boot_tests/malformed_index_settings/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/index_config_reloading/elasticsearch.yml rename to core/src/test/resources/boot_tests/malformed_index_settings/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/malformed_index_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/malformed_index_settings/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/malformed_index_settings/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/malformed_index_settings/readonlyrest_index.yml b/core/src/test/resources/boot_tests/malformed_index_settings/readonlyrest_index.yml new file mode 100644 index 0000000000..ad55e12c81 --- /dev/null +++ b/core/src/test/resources/boot_tests/malformed_index_settings/readonlyrest_index.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "MODIFIED: ADMIN" + auth_key: "admin:admin # wrong YAML \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/no_es_api_ssl_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/no_es_api_ssl_settings/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/no_es_api_ssl_settings/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/no_index_config_file_config_provided/readonlyrest.yml b/core/src/test/resources/boot_tests/no_index_config_file_config_provided/readonlyrest.yml deleted file mode 100644 index 52df94de84..0000000000 --- a/core/src/test/resources/boot_tests/no_index_config_file_config_provided/readonlyrest.yml +++ /dev/null @@ -1,9 +0,0 @@ -readonlyrest: - - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/no_index_config_file_config_provided/elasticsearch.yml b/core/src/test/resources/boot_tests/no_index_settings_file_settings_provided/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/no_index_config_file_config_provided/elasticsearch.yml rename to core/src/test/resources/boot_tests/no_index_settings_file_settings_provided/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/no_index_settings_file_settings_provided/readonlyrest.yml b/core/src/test/resources/boot_tests/no_index_settings_file_settings_provided/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/no_index_settings_file_settings_provided/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/no_internode_ssl_settings/readonlyrest.yml b/core/src/test/resources/boot_tests/no_internode_ssl_settings/readonlyrest.yml new file mode 100644 index 0000000000..de26466d1e --- /dev/null +++ b/core/src/test/resources/boot_tests/no_internode_ssl_settings/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/ror_fisb_ssl_declared_in_readonlyrest_file_xpack_security_enabled/readonlyrest.yml b/core/src/test/resources/boot_tests/ror_fisb_ssl_declared_in_readonlyrest_file_xpack_security_enabled/readonlyrest.yml index b61794270e..39b656d917 100644 --- a/core/src/test/resources/boot_tests/ror_fisb_ssl_declared_in_readonlyrest_file_xpack_security_enabled/readonlyrest.yml +++ b/core/src/test/resources/boot_tests/ror_fisb_ssl_declared_in_readonlyrest_file_xpack_security_enabled/readonlyrest.yml @@ -1,10 +1,12 @@ readonlyrest: + fips_mode: SSL_ONLY + ssl: + enable: true + keystore_file: "ror-keystore.jks" + keystore_pass: readonlyrest + key_pass: readonlyrest access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/elasticsearch.yml b/core/src/test/resources/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/elasticsearch.yml index 62e18f4e9c..66ad8fd762 100644 --- a/core/src/test/resources/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/elasticsearch.yml +++ b/core/src/test/resources/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/elasticsearch.yml @@ -5,7 +5,7 @@ xpack.security.enabled: true readonlyrest: force_load_from_file: true ssl_internode: - enable: false + enable: true keystore_file: "ror-keystore.jks" keystore_pass: readonlyrest1 key_pass: readonlyrest2 \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/readonlyrest.yml b/core/src/test/resources/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/readonlyrest.yml index 83d570b987..6e056f806f 100644 --- a/core/src/test/resources/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/readonlyrest.yml +++ b/core/src/test/resources/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/readonlyrest.yml @@ -1,18 +1,5 @@ readonlyrest: - ssl_internode: - enable: true - keystore_file: "ror-keystore.jks" - keystore_pass: readonlyrest1 - key_pass: readonlyrest2 - truststore_file: "ror-truststore.jks" - truststore_pass: readonlyrest3 - certificate_verification: true - access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/ror_ssl_declared_in_readonlyrest_file_xpack_security_enabled/readonlyrest.yml b/core/src/test/resources/boot_tests/ror_ssl_declared_in_readonlyrest_file_xpack_security_enabled/readonlyrest.yml index 83d570b987..3aeb9c309d 100644 --- a/core/src/test/resources/boot_tests/ror_ssl_declared_in_readonlyrest_file_xpack_security_enabled/readonlyrest.yml +++ b/core/src/test/resources/boot_tests/ror_ssl_declared_in_readonlyrest_file_xpack_security_enabled/readonlyrest.yml @@ -10,9 +10,5 @@ readonlyrest: certificate_verification: true access_control_rules: - - # ES container initializer need this rule to configure ES instance after startup - - name: "CONTAINER ADMIN" - verbosity: error - type: allow - auth_key: admin:container \ No newline at end of file + - name: "ADMIN" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/malformed_index_config/elasticsearch.yml b/core/src/test/resources/boot_tests/settings_reloading/elasticsearch.yml similarity index 100% rename from core/src/test/resources/boot_tests/malformed_index_config/elasticsearch.yml rename to core/src/test/resources/boot_tests/settings_reloading/elasticsearch.yml diff --git a/core/src/test/resources/boot_tests/settings_reloading/readonlyrest.yml b/core/src/test/resources/boot_tests/settings_reloading/readonlyrest.yml new file mode 100644 index 0000000000..49b9cc0c4d --- /dev/null +++ b/core/src/test/resources/boot_tests/settings_reloading/readonlyrest.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "Admin block" + auth_key: admin:admin \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_first.yml b/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_first.yml new file mode 100644 index 0000000000..db6dd1ef47 --- /dev/null +++ b/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_first.yml @@ -0,0 +1,4 @@ +readonlyrest: + access_control_rules: + - name: "INDEX MODIFIED: ADMIN" + auth_key: admin:container \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_initial.yml b/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_initial.yml new file mode 100644 index 0000000000..131501db46 --- /dev/null +++ b/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_initial.yml @@ -0,0 +1,7 @@ +readonlyrest: + access_control_rules: + - name: "INDEX MODIFIED: ADMIN" + auth_key: admin:container + + - name: "User 1" + auth_key: user1:pass \ No newline at end of file diff --git a/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_second.yml b/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_second.yml new file mode 100644 index 0000000000..1cfbcfea56 --- /dev/null +++ b/core/src/test/resources/boot_tests/settings_reloading/readonlyrest_second.yml @@ -0,0 +1,8 @@ +readonlyrest: + access_control_rules: + + - name: "INDEX MODIFIED: ADMIN" + auth_key: admin:container + + - name: "User 2" + auth_key: user2:pass \ No newline at end of file diff --git a/core/src/test/resources/coredns-image/Dockerfile b/core/src/test/resources/coredns-image/Dockerfile index 4ca797e91e..d85069f443 100644 --- a/core/src/test/resources/coredns-image/Dockerfile +++ b/core/src/test/resources/coredns-image/Dockerfile @@ -1,7 +1,6 @@ -FROM coredns/coredns:1.8.0 +FROM coredns/coredns:1.13.2 EXPOSE 53/udp COPY Corefile /Corefile COPY conf /conf - diff --git a/core/src/test/scala/tech/beshu/ror/integration/ActionYamlLoadedAccessControlTest.scala b/core/src/test/scala/tech/beshu/ror/integration/ActionYamlLoadedAccessControlTest.scala index 86a4aae70b..2f61583296 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/ActionYamlLoadedAccessControlTest.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/ActionYamlLoadedAccessControlTest.scala @@ -26,7 +26,7 @@ import tech.beshu.ror.mocks.MockRequestContext class ActionYamlLoadedAccessControlTest extends AnyWordSpec with BaseYamlLoadedAccessControlTest with Inside { - override protected def configYaml: String = + override protected def settingsYaml: String = """ |readonlyrest: | @@ -64,7 +64,7 @@ class ActionYamlLoadedAccessControlTest extends AnyWordSpec with BaseYamlLoadedA } } "it is a test config request and the action name match pattern on the configured list" in { - val request = MockRequestContext.indices.copy(action = Action.RorAction.RorTestConfigAction) + val request = MockRequestContext.indices.copy(action = Action.RorAction.RorTestSettingsAction) val result = acl.handleRegularRequest(request).runSyncUnsafe() inside(result.result) { case Allow(_, block) => block.name.value should be("Allowed for test config action") @@ -78,7 +78,7 @@ class ActionYamlLoadedAccessControlTest extends AnyWordSpec with BaseYamlLoadedA } } "it is a config request and the action name match pattern on the configured list with old name" in { - val request = MockRequestContext.indices.copy(action = Action.RorAction.RorConfigAction) + val request = MockRequestContext.indices.copy(action = Action.RorAction.RorMainSettingsAction) val result = acl.handleRegularRequest(request).runSyncUnsafe() inside(result.result) { case Allow(_, block) => block.name.value should be("Allowed for config action") diff --git a/core/src/test/scala/tech/beshu/ror/integration/AuditOutputFormatTests.scala b/core/src/test/scala/tech/beshu/ror/integration/AuditOutputFormatTests.scala index 56adb7ac82..ef24aa1101 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/AuditOutputFormatTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/AuditOutputFormatTests.scala @@ -29,13 +29,12 @@ import tech.beshu.ror.accesscontrol.audit.sink.{AuditDataStreamCreator, DataStre import tech.beshu.ror.accesscontrol.audit.{AuditingTool, LoggingContext} import tech.beshu.ror.accesscontrol.domain.* import tech.beshu.ror.accesscontrol.logging.AccessControlListLoggingDecorator -import tech.beshu.ror.audit.AuditEnvironmentContext import tech.beshu.ror.audit.instances.BlockVerbosityAwareAuditLogSerializer import tech.beshu.ror.es.{DataStreamBasedAuditSinkService, DataStreamService, IndexBasedAuditSinkService} import tech.beshu.ror.mocks.MockRequestContext import tech.beshu.ror.syntax.* import tech.beshu.ror.utils.TestUjson.ujson -import tech.beshu.ror.utils.TestsUtils.{fullDataStreamName, header, nes, testAuditEnvironmentContext} +import tech.beshu.ror.utils.TestsUtils.{fullDataStreamName, header, nes, testEsNodeSettings} import java.time.{Clock, Instant, ZoneId} import scala.concurrent.duration.* @@ -44,7 +43,7 @@ import scala.language.postfixOps class AuditOutputFormatTests extends AnyWordSpec with BaseYamlLoadedAccessControlTest with MockFactory { - override protected def configYaml: String = + override protected def settingsYaml: String = """ |readonlyrest: | @@ -159,7 +158,6 @@ class AuditOutputFormatTests extends AnyWordSpec with BaseYamlLoadedAccessContro private def auditedAcl(indexBasedAuditSinkService: IndexBasedAuditSinkService, dataStreamBasedAuditSinkService: DataStreamBasedAuditSinkService) = { implicit val loggingContext: LoggingContext = LoggingContext(Set.empty) - implicit val auditEnvironmentContext: AuditEnvironmentContext = testAuditEnvironmentContext val settings = AuditingTool.AuditSettings( NonEmptyList.of( AuditSink.Enabled(Config.EsIndexBasedSink( @@ -172,7 +170,8 @@ class AuditOutputFormatTests extends AnyWordSpec with BaseYamlLoadedAccessContro RorAuditDataStream.default, AuditCluster.LocalAuditCluster )) - ) + ), + testEsNodeSettings ) val auditingTool = AuditingTool.create( settings = settings, diff --git a/core/src/test/scala/tech/beshu/ror/integration/BaseYamlLoadedAccessControlTest.scala b/core/src/test/scala/tech/beshu/ror/integration/BaseYamlLoadedAccessControlTest.scala index 73b770df4a..e496ebf43d 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/BaseYamlLoadedAccessControlTest.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/BaseYamlLoadedAccessControlTest.scala @@ -16,51 +16,58 @@ */ package tech.beshu.ror.integration -import cats.implicits.* +import better.files.File import monix.execution.Scheduler.Implicits.global +import squants.information.Megabytes +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.AccessControlList import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider import tech.beshu.ror.accesscontrol.blocks.mocks.{MocksProvider, NoOpMocksProvider} -import tech.beshu.ror.accesscontrol.domain.{IndexName, RorConfigurationIndex} -import tech.beshu.ror.accesscontrol.factory.{HttpClientsFactory, RawRorConfigBasedCoreFactory} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} +import tech.beshu.ror.accesscontrol.domain.{IndexName, RorSettingsIndex} +import tech.beshu.ror.accesscontrol.factory.{HttpClientsFactory, RawRorSettingsBasedCoreFactory} +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.implicits.* import tech.beshu.ror.mocks.{MockHttpClientsFactory, MockLdapConnectionPoolProvider} import tech.beshu.ror.providers.* +import tech.beshu.ror.settings.ror.RawRorSettingsYamlParser import tech.beshu.ror.utils.TestsPropertiesProvider -import tech.beshu.ror.utils.TestsUtils.{BlockContextAssertion, defaultEsVersionForTests, unsafeNes} +import tech.beshu.ror.utils.TestsUtils.{BlockContextAssertion, defaultEsVersionForTests, testEsNodeSettings, unsafeNes} trait BaseYamlLoadedAccessControlTest extends BlockContextAssertion { - protected def configYaml: String + protected def settingsYaml: String protected implicit def envVarsProvider: EnvVarsProvider = OsEnvVarsProvider protected implicit def propertiesProvider: TestsPropertiesProvider = TestsPropertiesProvider.default - private implicit val environmentConfig: EnvironmentConfig = new EnvironmentConfig( + private implicit val systemContext: SystemContext = new SystemContext( envVarsProvider = envVarsProvider, propertiesProvider = propertiesProvider ) - private val factory = new RawRorConfigBasedCoreFactory(defaultEsVersionForTests) + private val factory = { + val esEnv = EsEnv(File("/config"), File("/modules"), defaultEsVersionForTests, testEsNodeSettings) + new RawRorSettingsBasedCoreFactory(esEnv) + } protected val ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider = MockLdapConnectionPoolProvider protected val httpClientsFactory: HttpClientsFactory = MockHttpClientsFactory protected val mockProvider: MocksProvider = NoOpMocksProvider lazy val acl: AccessControlList = { - val aclEngineT = for { - config <- RawRorConfig - .fromString(configYaml) - .map(_.fold(err => throw new IllegalStateException(err.show), identity)) - core <- factory - .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), - httpClientsFactory, - ldapConnectionPoolProvider, - mockProvider - ) - .map(_.fold(err => throw new IllegalStateException(s"Cannot create ACL: $err"), identity)) - } yield core.accessControl - aclEngineT.runSyncUnsafe() + val yamlParser = new RawRorSettingsYamlParser(Megabytes(3)) + val rorSettings = yamlParser.fromString(settingsYaml) match { + case Right(value) => value + case Left(error) => throw new IllegalStateException(error.show) + } + factory + .createCoreFrom( + rorSettings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), + httpClientsFactory, + ldapConnectionPoolProvider, + mockProvider + ) + .map(_.fold(err => throw new IllegalStateException(s"Cannot create ACL: $err"), _.accessControl)) + .runSyncUnsafe() } } diff --git a/core/src/test/scala/tech/beshu/ror/integration/CaseInsensitiveGroupsWithProxyAuthAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/CaseInsensitiveGroupsWithProxyAuthAccessControlTests.scala index d0b5f5f65e..fed2f83009 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/CaseInsensitiveGroupsWithProxyAuthAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/CaseInsensitiveGroupsWithProxyAuthAccessControlTests.scala @@ -30,7 +30,7 @@ import tech.beshu.ror.utils.uniquelist.UniqueList class CaseInsensitiveGroupsWithProxyAuthAccessControlTests extends AnyWordSpec with BaseYamlLoadedAccessControlTest with Inside { - override protected def configYaml: String = + override protected def settingsYaml: String = """ |readonlyrest: | global_settings: diff --git a/core/src/test/scala/tech/beshu/ror/integration/CurrentGroupHandlingAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/CurrentGroupHandlingAccessControlTests.scala index 995ff4e01b..608c7560cd 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/CurrentGroupHandlingAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/CurrentGroupHandlingAccessControlTests.scala @@ -38,7 +38,7 @@ class CurrentGroupHandlingAccessControlTests private val kbn1SignatureKey = "123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456" private val jwt1SignatureKey = "123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456.123456" - override protected def configYaml: String = + override protected def settingsYaml: String = s""" |readonlyrest: | diff --git a/core/src/test/scala/tech/beshu/ror/integration/CurrentUserMetadataAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/CurrentUserMetadataAccessControlTests.scala index 180818e9c9..7e93f0181f 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/CurrentUserMetadataAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/CurrentUserMetadataAccessControlTests.scala @@ -76,7 +76,7 @@ class CurrentUserMetadataAccessControlTests override protected val httpClientsFactory: HttpClientsFactory = new AsyncHttpClientsFactory - override protected def configYaml: String = + override protected def settingsYaml: String = s""" |readonlyrest: | diff --git a/core/src/test/scala/tech/beshu/ror/integration/GroupsRuleAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/GroupsRuleAccessControlTests.scala index 4e76fb6eae..82bcfdb801 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/GroupsRuleAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/GroupsRuleAccessControlTests.scala @@ -45,7 +45,7 @@ class GroupsRuleAccessControlTests override protected val ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider = new UnboundidLdapConnectionPoolProvider - override protected def configYaml: String = + override protected def settingsYaml: String = s""" |readonlyrest: | diff --git a/core/src/test/scala/tech/beshu/ror/integration/IndicesYamlLoadedAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/IndicesYamlLoadedAccessControlTests.scala index 70a87ea548..e4a25f7d88 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/IndicesYamlLoadedAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/IndicesYamlLoadedAccessControlTests.scala @@ -27,7 +27,7 @@ import tech.beshu.ror.utils.TestsUtils.* class IndicesYamlLoadedAccessControlTests extends AnyWordSpec with BaseYamlLoadedAccessControlTest with Inside { - override protected def configYaml: String = + override protected def settingsYaml: String = """ |readonlyrest: | diff --git a/core/src/test/scala/tech/beshu/ror/integration/KibanaIndexAndAccessYamlLoadedAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/KibanaIndexAndAccessYamlLoadedAccessControlTests.scala index 8cc3153a86..a7f5210512 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/KibanaIndexAndAccessYamlLoadedAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/KibanaIndexAndAccessYamlLoadedAccessControlTests.scala @@ -36,7 +36,7 @@ class KibanaIndexAndAccessYamlLoadedAccessControlTests extends AnyWordSpec with BaseYamlLoadedAccessControlTest with Inside { - override protected def configYaml: String = + override protected def settingsYaml: String = """ |readonlyrest: | diff --git a/core/src/test/scala/tech/beshu/ror/integration/LdapConnectivityCheckYamlLoadedAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/LdapConnectivityCheckYamlLoadedAccessControlTests.scala index a41a6c0ed2..b82db817c8 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/LdapConnectivityCheckYamlLoadedAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/LdapConnectivityCheckYamlLoadedAccessControlTests.scala @@ -42,7 +42,7 @@ class LdapConnectivityCheckYamlLoadedAccessControlTests SingletonLdapContainers.ldap2 ) - override protected def configYaml: String = + override protected def settingsYaml: String = s"""readonlyrest: | | access_control_rules: diff --git a/core/src/test/scala/tech/beshu/ror/integration/LdapServerDiscoveryCheckYamlLoadedAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/LdapServerDiscoveryCheckYamlLoadedAccessControlTests.scala index d8e2fd02e7..bc2e5f13d6 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/LdapServerDiscoveryCheckYamlLoadedAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/LdapServerDiscoveryCheckYamlLoadedAccessControlTests.scala @@ -40,12 +40,17 @@ class LdapServerDiscoveryCheckYamlLoadedAccessControlTests override val container: Container = doNotCreateOnWindows(new LdapWithDnsContainer("LDAP1", "test_example.ldif")) - private lazy val ldapPort = container match { + private lazy val dnsHost = container match { + case ldap: LdapWithDnsContainer => ldap.dnsHost + case _ => "localhost" // Just some random value - this test suite is ignored on Windows and the container is not started. + } + + private lazy val dnsPort = container match { case ldap: LdapWithDnsContainer => ldap.dnsPort case _ => 1234 // Just some random value - this test suite is ignored on Windows and the container is not started. But we still have to define port - required by configuration below. } - override protected def configYaml: String = + override protected def settingsYaml: String = s"""readonlyrest: | | access_control_rules: @@ -56,7 +61,7 @@ class LdapServerDiscoveryCheckYamlLoadedAccessControlTests | ldaps: | - name: ldap1 | server_discovery: - | dns_url: "dns://localhost:$ldapPort" + | dns_url: "dns://$dnsHost:$dnsPort" | ha: ROUND_ROBIN | ssl_enabled: false # default true | ssl_trust_all_certs: true # default false diff --git a/core/src/test/scala/tech/beshu/ror/integration/RegularRequestAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/RegularRequestAccessControlTests.scala index 87ff35258e..e3d2454557 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/RegularRequestAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/RegularRequestAccessControlTests.scala @@ -38,7 +38,7 @@ import java.util.Base64 class RegularRequestAccessControlTests extends AnyWordSpec with BaseYamlLoadedAccessControlTest with Inside { - protected def configYaml: String = + protected def settingsYaml: String = """ |other_non_ror_settings: | diff --git a/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthYamlLoadedAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthYamlLoadedAccessControlTests.scala index 4e6850f897..30a866b002 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthYamlLoadedAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthYamlLoadedAccessControlTests.scala @@ -36,7 +36,7 @@ import tech.beshu.ror.utils.uniquelist.UniqueList class RorKbnAuthYamlLoadedAccessControlTests extends AnyWordSpec with BaseYamlLoadedAccessControlTest with Inside { - override protected def configYaml: String = + override protected def settingsYaml: String = """ |readonlyrest: | access_control_rules: diff --git a/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthenticationYamlLoadedAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthenticationYamlLoadedAccessControlTests.scala index ebf78d44b8..cedb2b2cb2 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthenticationYamlLoadedAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthenticationYamlLoadedAccessControlTests.scala @@ -34,7 +34,7 @@ import tech.beshu.ror.utils.misc.JwtUtils.ClaimKeyOps class RorKbnAuthenticationYamlLoadedAccessControlTests extends AnyWordSpec with BaseYamlLoadedAccessControlTest with Inside { - override protected def configYaml: String = + override protected def settingsYaml: String = """ |readonlyrest: | access_control_rules: @@ -74,7 +74,7 @@ class RorKbnAuthenticationYamlLoadedAccessControlTests """.stripMargin "An ACL" when { - "is configured using config above" should { + "is configured using settings above" should { "allow to proceed" when { "JWT token is defined with empty groups" in { val jwt = JwtUtils.Jwt( diff --git a/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthnAndAuthzYamlLoadedAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthnAndAuthzYamlLoadedAccessControlTests.scala index bc6c256cc4..62a3c990c2 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthnAndAuthzYamlLoadedAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/RorKbnAuthnAndAuthzYamlLoadedAccessControlTests.scala @@ -33,7 +33,7 @@ import tech.beshu.ror.utils.misc.JwtUtils.ClaimKeyOps class RorKbnAuthnAndAuthzYamlLoadedAccessControlTests extends AnyWordSpec with BaseYamlLoadedAccessControlTest with Inside { - override protected def configYaml: String = + override protected def settingsYaml: String = """ |readonlyrest: | access_control_rules: @@ -80,7 +80,7 @@ class RorKbnAuthnAndAuthzYamlLoadedAccessControlTests """.stripMargin "An ACL" when { - "is configured using config above" should { + "is configured using settings above" should { "allow to proceed" when { "JWT token is defined" in { val jwt = JwtUtils.Jwt( diff --git a/core/src/test/scala/tech/beshu/ror/integration/VariableResolvingYamlLoadedAccessControlTests.scala b/core/src/test/scala/tech/beshu/ror/integration/VariableResolvingYamlLoadedAccessControlTests.scala index a25542054b..3ac28ecefe 100644 --- a/core/src/test/scala/tech/beshu/ror/integration/VariableResolvingYamlLoadedAccessControlTests.scala +++ b/core/src/test/scala/tech/beshu/ror/integration/VariableResolvingYamlLoadedAccessControlTests.scala @@ -55,7 +55,7 @@ class VariableResolvingYamlLoadedAccessControlTests extends AnyWordSpec private lazy val (pub, secret) = Random.generateRsaRandomKeys - override protected def configYaml: String = + override protected def settingsYaml: String = s""" |readonlyrest: | @@ -373,7 +373,7 @@ class VariableResolvingYamlLoadedAccessControlTests extends AnyWordSpec .from(request) .withLoggedUser(DirectlyLoggedUser(User.Id("user9"))) .withKibanaAccess(KibanaAccess.RO) - .withKibanaIndex(ClusterIndexName.Local.kibanaDefault) + .withKibanaIndex(KibanaIndexName.default) .withKibanaMetadata( JsonTree.Object(Map( "a" -> JsonTree.Value(JsonValue.StringValue("jwt_value_j0,j3")), diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/EnabledAccessControlListTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/EnabledAccessControlListTests.scala index ddbb9b9e94..848ff6b2cb 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/EnabledAccessControlListTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/EnabledAccessControlListTests.scala @@ -154,7 +154,7 @@ class EnabledAccessControlListTests extends AnyWordSpec with MockFactory with In showBasicAuthPrompt = true, forbiddenRequestMessage = "Forbidden", flsEngine = FlsEngine.default, - configurationIndex = RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settingsIndex = RorSettingsIndex(IndexName.Full(".readonlyrest")), userIdCaseSensitivity = CaseSensitivity.Enabled, usersDefinitionDuplicateUsernamesValidationEnabled = true ), diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapUsersServiceNetworkRelatedTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapUsersServiceNetworkRelatedTests.scala index 0ce4201c5f..3807664658 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapUsersServiceNetworkRelatedTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/ldap/implementations/UnboundidLdapUsersServiceNetworkRelatedTests.scala @@ -146,7 +146,7 @@ class UnboundidLdapUsersServiceNetworkRelatedTests val ldapConnectionConfig = createLdapConnectionConfig( poolName = ldapId, connectionMethod = ConnectionMethod.SingleServer( - LdapHost.from(s"ldap://localhost:${ldap1ContainerWithToxiproxy.innerContainerMappedPort}").get + LdapHost.from(s"ldap://${ldap1ContainerWithToxiproxy.containerHost}:${ldap1ContainerWithToxiproxy.innerContainerMappedPort}").get ) ) val result = for { diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/user/UserDefinitionsValidatorTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/user/UserDefinitionsValidatorTests.scala index ae79f7fb54..8a6d390396 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/user/UserDefinitionsValidatorTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/definitions/user/UserDefinitionsValidatorTests.scala @@ -122,7 +122,7 @@ class UserDefinitionsValidatorTests extends AnyWordSpec with Matchers { showBasicAuthPrompt = false, forbiddenRequestMessage = "Forbidden", flsEngine = GlobalSettings.FlsEngine.default, - configurationIndex = RorConfigurationIndex(IndexName.Full(nes(".readonlyrest"))), + settingsIndex = RorSettingsIndex(IndexName.Full(nes(".readonlyrest"))), userIdCaseSensitivity = CaseSensitivity.Enabled, usersDefinitionDuplicateUsernamesValidationEnabled = validationEnabled ) diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/indices/IndicesRuleLocalIndexTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/indices/IndicesRuleLocalIndexTests.scala index 7625e366ed..4cf380702f 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/indices/IndicesRuleLocalIndexTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/indices/IndicesRuleLocalIndexTests.scala @@ -18,7 +18,6 @@ package tech.beshu.ror.unit.acl.blocks.rules.indices import cats.data.NonEmptySet import monix.eval.Task -import tech.beshu.ror.accesscontrol.orders.requestedIndexOrder import tech.beshu.ror.accesscontrol.blocks.BlockContext.MultiIndexRequestBlockContext.Indices import tech.beshu.ror.accesscontrol.domain.KibanaIndexName import tech.beshu.ror.accesscontrol.orders.custerIndexNameOrder @@ -245,26 +244,24 @@ trait IndicesRuleLocalIndexTests { ) } } - "some indices are excluded" when { - "todo" in { // todo - assertMatchRuleForIndexRequest( - configured = NonEmptySet.of(indexNameVar("test-index1-*")), - requestIndices = Set(requestedIndex("test-index*"), requestedIndex("-*old")), - modifyRequestContext = _.copy( - allIndicesAndAliases = Set( - fullLocalIndexWithAliases(fullIndexName("test-index1-0001")), - fullLocalIndexWithAliases(fullIndexName("test-index1-0002")), - fullLocalIndexWithAliases(fullIndexName("test-index1-old")), - fullLocalIndexWithAliases(fullIndexName("test-index2-0001")), - fullLocalIndexWithAliases(fullIndexName("test-index2-old")), - ) - ), - filteredRequestedIndices = Set( - requestedIndex("test-index1-0001"), - requestedIndex("test-index1-0002") - ), - ) - } + "some indices are excluded" in { + assertMatchRuleForIndexRequest( + configured = NonEmptySet.of(indexNameVar("test-index1-*")), + requestIndices = Set(requestedIndex("test-index*"), requestedIndex("-*old")), + modifyRequestContext = _.copy( + allIndicesAndAliases = Set( + fullLocalIndexWithAliases(fullIndexName("test-index1-0001")), + fullLocalIndexWithAliases(fullIndexName("test-index1-0002")), + fullLocalIndexWithAliases(fullIndexName("test-index1-old")), + fullLocalIndexWithAliases(fullIndexName("test-index2-0001")), + fullLocalIndexWithAliases(fullIndexName("test-index2-old")), + ) + ), + filteredRequestedIndices = Set( + requestedIndex("test-index1-0001"), + requestedIndex("test-index1-0002") + ), + ) } } "not match" when { diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/BaseKibanaAccessBasedTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/BaseKibanaAccessBasedTests.scala index 2252027489..406393f8ed 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/BaseKibanaAccessBasedTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/BaseKibanaAccessBasedTests.scala @@ -257,12 +257,12 @@ abstract class BaseKibanaAccessBasedTests[RULE <: Rule : RuleName, SETTINGS] )() assertMatchRuleUsingIndicesRequest( settingsOf(KibanaAccess.Admin), - Action.RorAction.RorOldConfigAction, + Action.RorAction.RorRefreshSettingsAction, requestedIndices = Set(RequestedIndex(Local(rorIndex), excluded = false)) )() assertMatchRuleUsingIndicesRequest( settingsOf(KibanaAccess.Admin), - Action.RorAction.RorConfigAction, + Action.RorAction.RorMainSettingsAction, requestedIndices = Set(RequestedIndex(Local(rorIndex), excluded = false)) )() assertMatchRuleUsingIndicesRequest( diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/KibanaAccessRuleTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/KibanaAccessRuleTests.scala index 0b233b226c..fce23af245 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/KibanaAccessRuleTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/KibanaAccessRuleTests.scala @@ -30,7 +30,7 @@ class KibanaAccessRuleTests override protected def settingsOf(access: KibanaAccess, customKibanaIndex: Option[KibanaIndexName] = None): Settings = - Settings(access, RorConfigurationIndex(rorIndex)) + Settings(access, RorSettingsIndex(rorIndex)) override protected def defaultOutputBlockContextAssertion(settings: Settings, indices: Set[RequestedIndex[ClusterIndexName]], diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/KibanaUserDataRuleTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/KibanaUserDataRuleTests.scala index bc2b40c955..fb58caf0ca 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/KibanaUserDataRuleTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/blocks/rules/kibana/KibanaUserDataRuleTests.scala @@ -53,12 +53,12 @@ class KibanaUserDataRuleTests val kibanaTemplateIndex = kibanaIndexName("kibana_template_index") val rule = createRuleFrom(KibanaUserDataRule.Settings( access = KibanaAccess.Unrestricted, - kibanaIndex = AlreadyResolved(ClusterIndexName.Local.kibanaDefault), + kibanaIndex = AlreadyResolved(KibanaIndexName.default), kibanaTemplateIndex = Some(AlreadyResolved(kibanaTemplateIndex)), appsToHide = Set.empty, allowedApiPaths = Set.empty, metadata = None, - rorIndex = RorConfigurationIndex(rorIndex) + rorIndex = RorSettingsIndex(rorIndex) )) val blockContext = checkRule(rule) blockContext.userMetadata should be { @@ -67,7 +67,7 @@ class KibanaUserDataRuleTests .withLoggedUser(LoggedUser.DirectlyLoggedUser(User.Id("user1"))) .withCurrentGroupId(GroupId("mygroup")) .withKibanaAccess(KibanaAccess.Unrestricted) - .withKibanaIndex(ClusterIndexName.Local.kibanaDefault) + .withKibanaIndex(KibanaIndexName.default) .withKibanaTemplateIndex(kibanaTemplateIndex) } } @@ -77,12 +77,12 @@ class KibanaUserDataRuleTests val apps: UniqueNonEmptyList[KibanaApp] = UniqueNonEmptyList.of(FullNameKibanaApp("app1"), FullNameKibanaApp("app2")) val rule = createRuleFrom(KibanaUserDataRule.Settings( access = KibanaAccess.Unrestricted, - kibanaIndex = AlreadyResolved(ClusterIndexName.Local.kibanaDefault), + kibanaIndex = AlreadyResolved(KibanaIndexName.default), kibanaTemplateIndex = None, appsToHide = apps.toCovariantSet, allowedApiPaths = Set.empty, metadata = None, - rorIndex = RorConfigurationIndex(rorIndex) + rorIndex = RorSettingsIndex(rorIndex) )) val blockContext = checkRule(rule) blockContext.userMetadata should be { @@ -91,7 +91,7 @@ class KibanaUserDataRuleTests .withLoggedUser(LoggedUser.DirectlyLoggedUser(User.Id("user1"))) .withCurrentGroupId(GroupId("mygroup")) .withKibanaAccess(KibanaAccess.Unrestricted) - .withKibanaIndex(ClusterIndexName.Local.kibanaDefault) + .withKibanaIndex(KibanaIndexName.default) .withHiddenKibanaApps(apps) } } @@ -114,12 +114,12 @@ class KibanaUserDataRuleTests ) val rule = createRuleFrom(KibanaUserDataRule.Settings( access = KibanaAccess.Unrestricted, - kibanaIndex = AlreadyResolved(ClusterIndexName.Local.kibanaDefault), + kibanaIndex = AlreadyResolved(KibanaIndexName.default), kibanaTemplateIndex = None, appsToHide = Set.empty, allowedApiPaths = paths.toCovariantSet, metadata = None, - rorIndex = RorConfigurationIndex(rorIndex) + rorIndex = RorSettingsIndex(rorIndex) )) val blockContext = checkRule(rule) blockContext.userMetadata should be { @@ -128,7 +128,7 @@ class KibanaUserDataRuleTests .withLoggedUser(LoggedUser.DirectlyLoggedUser(User.Id("user1"))) .withCurrentGroupId(GroupId("mygroup")) .withKibanaAccess(KibanaAccess.Unrestricted) - .withKibanaIndex(ClusterIndexName.Local.kibanaDefault) + .withKibanaIndex(KibanaIndexName.default) .withAllowedKibanaApiPaths(paths) } } @@ -156,12 +156,12 @@ class KibanaUserDataRuleTests } val rule = createRuleFrom(KibanaUserDataRule.Settings( access = KibanaAccess.Unrestricted, - kibanaIndex = AlreadyResolved(ClusterIndexName.Local.kibanaDefault), + kibanaIndex = AlreadyResolved(KibanaIndexName.default), kibanaTemplateIndex = None, appsToHide = Set.empty, allowedApiPaths = Set.empty, metadata = Option(resolvableMetadataJsonRepresentation), - rorIndex = RorConfigurationIndex(rorIndex) + rorIndex = RorSettingsIndex(rorIndex) )) val blockContext = checkRule(rule) blockContext.userMetadata should be { @@ -170,7 +170,7 @@ class KibanaUserDataRuleTests .withLoggedUser(LoggedUser.DirectlyLoggedUser(User.Id("user1"))) .withCurrentGroupId(GroupId("mygroup")) .withKibanaAccess(KibanaAccess.Unrestricted) - .withKibanaIndex(ClusterIndexName.Local.kibanaDefault) + .withKibanaIndex(KibanaIndexName.default) .withKibanaMetadata( JsonTree.Object(Map( "a" -> JsonTree.Value(NumValue(1)), @@ -221,12 +221,12 @@ class KibanaUserDataRuleTests customKibanaIndex: Option[KibanaIndexName] = None): KibanaUserDataRule.Settings = KibanaUserDataRule.Settings( access = access, - kibanaIndex = AlreadyResolved(customKibanaIndex.getOrElse(ClusterIndexName.Local.kibanaDefault)), + kibanaIndex = AlreadyResolved(customKibanaIndex.getOrElse(KibanaIndexName.default)), kibanaTemplateIndex = None, appsToHide = Set.empty, allowedApiPaths = Set.empty, metadata = None, - rorIndex = RorConfigurationIndex(rorIndex) + rorIndex = RorSettingsIndex(rorIndex) ) override protected def defaultOutputBlockContextAssertion(settings: KibanaUserDataRule.Settings, diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/AuditSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/AuditSettingsTests.scala index 34c774f823..62863cab4a 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/AuditSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/AuditSettingsTests.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.unit.acl.factory +import better.files.* import cats.data.NonEmptyList import eu.timepit.refined.types.string.NonEmptyString import io.circe.{Json, parser} @@ -25,25 +26,26 @@ import org.json.JSONObject import org.scalatest.Inside import org.scalatest.matchers.should.Matchers.* import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.accesscontrol.audit.AuditFieldUtils +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.AuditingTool.AuditSettings.AuditSink import tech.beshu.ror.accesscontrol.audit.AuditingTool.AuditSettings.AuditSink.Config import tech.beshu.ror.accesscontrol.audit.configurable.ConfigurableAuditLogSerializer import tech.beshu.ror.accesscontrol.audit.ecs.EcsV1AuditLogSerializer +import tech.beshu.ror.accesscontrol.audit.{AuditEnvironmentContextBasedOnEsNodeSettings, AuditFieldUtils} import tech.beshu.ror.accesscontrol.blocks.mocks.NoOpMocksProvider import tech.beshu.ror.accesscontrol.domain.AuditCluster.* -import tech.beshu.ror.accesscontrol.domain.{AuditCluster, IndexName, RorAuditLoggerName, RorConfigurationIndex} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.AuditingSettingsCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.{Core, RawRorConfigBasedCoreFactory} +import tech.beshu.ror.accesscontrol.domain.{AuditCluster, IndexName, RorAuditLoggerName, RorSettingsIndex} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.AuditingSettingsCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.{Core, RawRorSettingsBasedCoreFactory, RorDependencies} import tech.beshu.ror.audit.* import tech.beshu.ror.audit.AuditResponseContext.Verbosity import tech.beshu.ror.audit.adapters.{DeprecatedAuditLogSerializerAdapter, EnvironmentAwareAuditLogSerializerAdapter} import tech.beshu.ror.audit.instances.* import tech.beshu.ror.audit.utils.AuditSerializationHelper.{AllowedEventMode, AuditFieldPath, AuditFieldValueDescriptor} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig, RorConfig} -import tech.beshu.ror.es.EsVersion +import tech.beshu.ror.es.{EsEnv, EsVersion} import tech.beshu.ror.mocks.{MockHttpClientsFactory, MockLdapConnectionPoolProvider} +import tech.beshu.ror.settings.ror.RawRorSettings import tech.beshu.ror.utils.TestsUtils.* import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList @@ -53,8 +55,9 @@ import scala.reflect.ClassTag class AuditSettingsTests extends AnyWordSpec with Inside { private def factory(esVersion: EsVersion = defaultEsVersionForTests) = { - implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - new RawRorConfigBasedCoreFactory(esVersion) + implicit val systemContext: SystemContext = SystemContext.default + val esEnv = EsEnv(File("/config"), File("/modules"), esVersion, testEsNodeSettings) + new RawRorSettingsBasedCoreFactory(esEnv) } private val zonedDateTime = ZonedDateTime.of(2019, 1, 1, 0, 1, 59, 0, ZoneId.of("+1")) @@ -62,7 +65,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { "Audit settings" when { "audit is not configured" should { "be disabled by default" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -75,23 +78,23 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) - assertSettingsNoPresent(config) + assertSettingsNoPresent(settings) } } "audit is disabled" should { "be disabled" when { "one line audit format" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit.enabled: false """.stripMargin ) - assertSettingsNoPresent(config) + assertSettingsNoPresent(settings) } "multi line audit format" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -99,16 +102,16 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) - assertSettingsNoPresent(config) + assertSettingsNoPresent(settings) } } } "audit is enabled" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "no outputs defined" should { "fallback to default index based audit sink" when { "one line audit format" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit.enabled: true @@ -116,13 +119,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "multi line audit format" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -131,7 +134,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) @@ -139,7 +142,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } "simple format is used" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -150,14 +153,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { val core = factory() .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider ) .runSyncUnsafe() - inside(core) { case Right(Core(_, RorConfig(_, _, _, Some(auditingSettings)))) => + inside(core) { case Right(Core(_, RorDependencies(_, _, _), Some(auditingSettings))) => auditingSettings.auditSinks.size should be(3) val sink1 = auditingSettings.auditSinks.head @@ -189,7 +192,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "'log' output type defined" when { "only type is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -200,13 +203,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertLogBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedLoggerName = "readonlyrest_audit" ) } "the output's enabled flag is set" when { "set to true" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -218,12 +221,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertLogBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedLoggerName = "readonlyrest_audit" ) } "set to false" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -235,13 +238,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertSettings( - config, - expectedConfigs = NonEmptyList.of(AuditSink.Disabled) + settings, + expectedAuditSinks = NonEmptyList.of(AuditSink.Disabled) ) } } "custom logger name is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -253,12 +256,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertLogBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedLoggerName = "custom_logger" ) } "custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -270,12 +273,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertLogBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedLoggerName = "readonlyrest_audit" ) } "deprecated custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -287,12 +290,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertLogBasedAuditSinkSettingsPresent[DeprecatedAuditLogSerializerAdapter[_]]( - config, + settings, expectedLoggerName = "readonlyrest_audit" ) } "all custom settings are set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -305,12 +308,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertLogBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedLoggerName = "custom_logger" ) } "configurable serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -337,11 +340,11 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertLogBasedAuditSinkSettingsPresent[ConfigurableAuditLogSerializer]( - config, + settings, expectedLoggerName = "readonlyrest_audit" ) - val configuredSerializer = serializer(config).asInstanceOf[ConfigurableAuditLogSerializer] + val configuredSerializer = serializer(settings).asInstanceOf[ConfigurableAuditLogSerializer] configuredSerializer.allowedEventMode shouldBe AllowedEventMode.Include(Set(Verbosity.Info)) configuredSerializer.fields shouldBe AuditFieldUtils.fields( @@ -367,7 +370,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "'index' output type defined" when { "only type is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -378,14 +381,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "the output's enabled flag is set" when { "set to true" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -397,13 +400,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "set to false" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -415,13 +418,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertSettings( - config, - expectedConfigs = NonEmptyList.of(AuditSink.Disabled) + settings, + expectedAuditSinks = NonEmptyList.of(AuditSink.Disabled) ) } } "custom audit index name is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -433,14 +436,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "custom_template_20181231", expectedAuditCluster = LocalAuditCluster ) } "serializer is set" when { "QueryAuditLogSerializer serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -452,13 +455,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "QueryAuditLogSerializer serializer is set and correctly serializes event without logged user" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -470,11 +473,11 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) - val createdSerializer = serializer(config) + val createdSerializer = serializer(settings) val serializedResponse = createdSerializer.onResponse( AuditResponseContext.Forbidden(new DummyAuditRequestContext(loggedInUserName = None, attemptedUserName = None)) ) @@ -485,7 +488,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { serializedResponse.get.isNull("logged_user") } "QueryAuditLogSerializer serializer is set and correctly serializes event with logged user" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -497,11 +500,11 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) - val createdSerializer = serializer(config) + val createdSerializer = serializer(settings) val serializedResponse = createdSerializer.onResponse( AuditResponseContext.Forbidden(new DummyAuditRequestContext(loggedInUserName = Some("my_user"))) ) @@ -512,7 +515,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { serializedResponse.get.get("logged_user") shouldBe "my_user" } "custom environment-aware serializer is set and correctly serializes events" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -524,11 +527,11 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertDataStreamAuditSinkSettingsPresent[EnvironmentAwareAuditLogSerializerAdapter]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = LocalAuditCluster ) - val createdSerializer = serializer(config) + val createdSerializer = serializer(settings) val serializedResponse = createdSerializer.onResponse(AuditResponseContext.Forbidden(new DummyAuditRequestContext)) serializedResponse shouldBe defined @@ -536,7 +539,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { serializedResponse.get.get("custom_field_for_es_cluster_name") shouldBe "testEsCluster" } "ECS serializer is set (including request content)" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -551,11 +554,11 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[EcsV1AuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) - val createdSerializer = serializer(config) + val createdSerializer = serializer(settings) val serializedResponse = createdSerializer.onResponse(AuditResponseContext.Forbidden(new DummyAuditRequestContext)) val expectedJsonStr = @@ -614,7 +617,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { actualJson should be(expectedJson) } "ECS serializer is set (not including request content)" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -629,11 +632,11 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[EcsV1AuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) - val createdSerializer = serializer(config) + val createdSerializer = serializer(settings) val serializedResponse = createdSerializer.onResponse(AuditResponseContext.Forbidden(new DummyAuditRequestContext)) val expectedJsonStr = @@ -691,7 +694,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { actualJson should be(expectedJson) } "ECS serializer is set (not including request content by default)" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -705,11 +708,11 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[EcsV1AuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) - val createdSerializer = serializer(config) + val createdSerializer = serializer(settings) val serializedResponse = createdSerializer.onResponse(AuditResponseContext.Forbidden(new DummyAuditRequestContext)) val expectedJsonStr = @@ -767,7 +770,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { actualJson should be(expectedJson) } "deprecated custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -779,7 +782,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[DeprecatedAuditLogSerializerAdapter[_]]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) @@ -787,7 +790,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "audit cluster is set" when { "array syntax for cluster" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -798,7 +801,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -808,7 +811,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "array syntax for cluster with credentials" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -819,7 +822,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of( @@ -832,7 +835,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "extended syntax for cluster with round-robin mode" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -845,7 +848,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -855,7 +858,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "extended syntax for cluster with credentials" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -870,7 +873,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of( @@ -885,7 +888,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } "all audit settings are custom" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -899,7 +902,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedIndexName = "custom_template_20181231", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -911,7 +914,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "'data_stream' output type defined" when { "only type is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -922,14 +925,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertDataStreamAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = LocalAuditCluster ) } "the output's enabled flag is set" when { "set to true" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -941,13 +944,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertDataStreamAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = LocalAuditCluster ) } "set to false" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -959,13 +962,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertSettings( - config, - expectedConfigs = NonEmptyList.of(AuditSink.Disabled) + settings, + expectedAuditSinks = NonEmptyList.of(AuditSink.Disabled) ) } } "custom audit data stream name is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -977,14 +980,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertDataStreamAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedDataStreamName = "custom_audit_data_stream", expectedAuditCluster = LocalAuditCluster ) } "serializer is set" when { "custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -996,13 +999,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertDataStreamAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = LocalAuditCluster ) } "deprecated custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1014,7 +1017,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertDataStreamAuditSinkSettingsPresent[DeprecatedAuditLogSerializerAdapter[_]]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = LocalAuditCluster ) @@ -1022,7 +1025,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "audit cluster is set" when { "array syntax for cluster" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1033,7 +1036,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) assertDataStreamAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -1043,7 +1046,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "array syntax for cluster with credentials" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1054,7 +1057,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) assertDataStreamAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("https://user:pass@1.1.1.1:9200"))), @@ -1064,7 +1067,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "extended syntax for cluster with round-robin mode" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1077,7 +1080,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) assertDataStreamAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -1087,7 +1090,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "extended syntax for cluster with credentials" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1102,7 +1105,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) assertDataStreamAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedDataStreamName = "readonlyrest_audit", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of( @@ -1117,7 +1120,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } "all audit settings are custom" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1135,7 +1138,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertDataStreamAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedDataStreamName = "custom_audit_data_stream", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of( @@ -1159,7 +1162,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { EsVersion(7, 9, 0), ) - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1174,7 +1177,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { esVersions.foreach { esVersion => assertDataStreamAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedDataStreamName = "custom_audit_data_stream", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -1188,7 +1191,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "all output types defined" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1203,14 +1206,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { val core = factory() .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider ) .runSyncUnsafe() - inside(core) { case Right(Core(_, RorConfig(_, _, _, Some(auditingSettings)))) => + inside(core) { case Right(Core(_, RorDependencies(_, _, _), Some(auditingSettings))) => auditingSettings.auditSinks.size should be(3) val sink1 = auditingSettings.auditSinks.head @@ -1241,7 +1244,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } "one of outputs is disabled" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1256,14 +1259,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { val core = factory() .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider ) .runSyncUnsafe() - inside(core) { case Right(Core(_, RorConfig(_, _, _, Some(auditingSettings)))) => + inside(core) { case Right(Core(_, RorDependencies(_, _, _), Some(auditingSettings))) => auditingSettings.auditSinks.size should be(2) val sink1 = auditingSettings.auditSinks.head @@ -1278,10 +1281,10 @@ class AuditSettingsTests extends AnyWordSpec with Inside { sink2Config.logSerializer shouldBe a[QueryAuditLogSerializer] } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "'log' output type" when { "not supported custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1293,12 +1296,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Class tech.beshu.ror.accesscontrol.blocks.RuleOrdering is not a subclass of tech.beshu.ror.audit.AuditLogSerializer or tech.beshu.ror.requestcontext.AuditLogSerializer" ) } "logger name is empty" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1310,14 +1313,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "The audit 'logger_name' cannot be empty" ) } } "'index' output type" when { "not supported custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1329,12 +1332,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Class tech.beshu.ror.accesscontrol.blocks.RuleOrdering is not a subclass of tech.beshu.ror.audit.AuditLogSerializer or tech.beshu.ror.requestcontext.AuditLogSerializer" ) } "custom audit index name pattern is invalid" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1346,12 +1349,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Illegal pattern specified for audit index template. Have you misplaced quotes? Search for 'DateTimeFormatter patterns' to learn the syntax. Pattern was: invalid pattern error: Unknown pattern letter: i" ) } "remote cluster is empty list (array syntax)" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1363,7 +1366,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Non empty list of valid URI is required" ) } @@ -1376,7 +1379,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) tests.foreach { auditNodes => - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( s""" |readonlyrest: | audit: @@ -1390,13 +1393,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = s"One or more audit cluster nodes have inconsistent credentials. Please configure the same credentials. Nodes: ${auditNodes.mkString(", ")}" ) } tests.foreach { auditNodes => - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( s""" |readonlyrest: | audit: @@ -1408,14 +1411,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = s"One or more audit cluster nodes have inconsistent credentials. Please configure the same credentials. Nodes: ${auditNodes.mkString(", ")}" ) } } "remote cluster is empty list (extended syntax)" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1429,12 +1432,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'nodes': Non empty list of valid URI is required" ) } "remote cluster has invalid mode" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1448,13 +1451,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'mode': Unknown cluster mode [not-existing-mode], allowed values are: [round-robin]" ) } "remote cluster credentials malformed" when { "password not provided" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1469,12 +1472,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Audit output configuration is missing the ‘password’ field." ) } "username not provided" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1489,7 +1492,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Audit output configuration is missing the ‘username’ field." ) } @@ -1497,7 +1500,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "'data_stream' output type" when { "not supported custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1509,12 +1512,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Class tech.beshu.ror.accesscontrol.blocks.RuleOrdering is not a subclass of tech.beshu.ror.audit.AuditLogSerializer or tech.beshu.ror.requestcontext.AuditLogSerializer" ) } "data stream name is invalid" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1526,7 +1529,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'data_stream': Illegal format for ROR audit 'data_stream' name - Data stream '.ds-INVALID-data-stream-name#' has an invalid format. Cause: " + "name must be lowercase, " + "name must not contain forbidden characters '\\', '/', '*', '?', '\"', '<', '>', '|', ',', '#', ':', ' ', " + @@ -1535,7 +1538,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "remote cluster is empty list" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1547,7 +1550,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'cluster': Non empty list of valid URI is required" ) } @@ -1563,7 +1566,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { EsVersion(5, 0, 5), ) - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1575,7 +1578,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { esVersions.foreach { esVersion => assertInvalidSettings( - config, + settings, expectedErrorMessage = s"Error for field 'type': Data stream audit output is supported from Elasticsearch version 7.9.0, " + s"but your version is ${esVersion.major}.${esVersion.minor}.${esVersion.revision}. Use 'index' type or upgrade to 7.9.0 or later.", esVersion = esVersion @@ -1584,7 +1587,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } "unknown output type is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1595,12 +1598,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'type': Unsupported type of audit output: custom_type. Supported types: [data_stream, index, log]" ) } "unknown output type is set when using simple format" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1610,18 +1613,18 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Unsupported type of audit output: custom_type. Supported types: [data_stream, index, log]" ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Unsupported type of audit output: custom_type. Supported types: [index, log]", esVersion = EsVersion(7, 8, 0) ) } "'outputs' array is empty" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1631,12 +1634,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "The audit 'outputs' array cannot be empty" ) } "configurable serializer is set with invalid value descriptor" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1655,12 +1658,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Configurable serializer is used, but the 'fields' setting is missing or invalid: There are invalid placeholder values: HTTP_METHOD2" ) } "configurable serializer is set, but without fields setting" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1674,7 +1677,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Configurable serializer is used, but the 'fields' setting is missing or invalid: Missing required field" ) } @@ -1682,7 +1685,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "deprecated format is used" should { "ignore deprecated fields when both formats are used at once" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1696,7 +1699,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) @@ -1704,7 +1707,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { "be optional" when { "audit collector is disabled" when { "'audit' section is defined" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1712,24 +1715,24 @@ class AuditSettingsTests extends AnyWordSpec with Inside { """.stripMargin ) - assertSettingsNoPresent(config) + assertSettingsNoPresent(settings) } "'audit' section is not defined" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit_collector: false """.stripMargin ) - assertSettingsNoPresent(config) + assertSettingsNoPresent(settings) } } } - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "'audit' section is defined" when { "audit collector is enabled" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1738,13 +1741,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "custom audit index name is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1754,13 +1757,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "custom_template_20181231", expectedAuditCluster = LocalAuditCluster ) } "custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1770,13 +1773,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "deprecated custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1786,13 +1789,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[DeprecatedAuditLogSerializerAdapter[_]]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "custom audit cluster is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1802,7 +1805,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -1812,7 +1815,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "all audit settings are custom" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1824,7 +1827,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedIndexName = "custom_template_20181231", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -1836,7 +1839,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } "'audit' section is not defined" when { "audit collector is enabled" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit_collector: true @@ -1844,13 +1847,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "custom audit index name is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit_collector: true @@ -1859,13 +1862,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "custom_template_20181231", expectedAuditCluster = LocalAuditCluster ) } "custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit_collector: true @@ -1874,13 +1877,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "deprecated custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit_collector: true @@ -1889,13 +1892,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[DeprecatedAuditLogSerializerAdapter[_]]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = LocalAuditCluster ) } "custom audit cluster is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit_collector: true @@ -1904,7 +1907,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[BlockVerbosityAwareAuditLogSerializer]( - config, + settings, expectedIndexName = "readonlyrest_audit-2018-12-31", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("http://user:test@1.1.1.1"))), @@ -1914,7 +1917,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) } "all audit settings are custom" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit_collector: true @@ -1925,7 +1928,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertIndexBasedAuditSinkSettingsPresent[QueryAuditLogSerializer]( - config, + settings, expectedIndexName = "custom_template_20181231", expectedAuditCluster = RemoteAuditCluster( nodes = UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("1.1.1.1"))), @@ -1937,10 +1940,10 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "'audit' section is defined" when { "not supported custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1950,12 +1953,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Class tech.beshu.ror.accesscontrol.blocks.RuleOrdering is not a subclass of tech.beshu.ror.audit.AuditLogSerializer or tech.beshu.ror.requestcontext.AuditLogSerializer" ) } "custom audit index name pattern is invalid" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """ |readonlyrest: | audit: @@ -1965,12 +1968,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'index_template': Illegal pattern specified for audit index template. Have you misplaced quotes? Search for 'DateTimeFormatter patterns' to learn the syntax. Pattern was: invalid pattern error: Unknown pattern letter: i" ) } "remote cluster is empty list" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """readonlyrest: | audit: | collector: true @@ -1979,14 +1982,14 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'cluster': Non empty list of valid URI is required" ) } } "'audit' section is not defined" when { "not supported custom serializer is set" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """readonlyrest: | audit_collector: true | audit_serializer: "tech.beshu.ror.accesscontrol.blocks.RuleOrdering" @@ -1994,12 +1997,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Class tech.beshu.ror.accesscontrol.blocks.RuleOrdering is not a subclass of tech.beshu.ror.audit.AuditLogSerializer or tech.beshu.ror.requestcontext.AuditLogSerializer" ) } "custom audit index name pattern is invalid" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """readonlyrest: | audit_collector: true | audit_index_template: "invalid pattern" @@ -2007,12 +2010,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'audit_index_template': Illegal pattern specified for audit index template. Have you misplaced quotes? Search for 'DateTimeFormatter patterns' to learn the syntax. Pattern was: invalid pattern error: Unknown pattern letter: i" ) } "remote cluster is empty list" in { - val config = rorConfigWithAuditUnsafe( + val settings = rorSettingsWithAuditUnsafe( """readonlyrest: | audit_collector: true | audit_cluster: [] @@ -2020,7 +2023,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { ) assertInvalidSettings( - config, + settings, expectedErrorMessage = "Error for field 'audit_cluster': Non empty list of valid URI is required" ) } @@ -2030,8 +2033,8 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } - private def rorConfigWithAuditUnsafe(auditSection: String) = { - val rawConfig = + private def rorSettingsWithAuditUnsafe(auditSection: String) = { + val rawSettings = s"""$auditSection | access_control_rules: | @@ -2039,51 +2042,51 @@ class AuditSettingsTests extends AnyWordSpec with Inside { | type: allow | auth_key: admin:container """.stripMargin - rorConfigFromUnsafe(rawConfig) + rorSettingsFromUnsafe(rawSettings) } - private def assertSettingsNoPresent(config: RawRorConfig): Unit = { + private def assertSettingsNoPresent(settings: RawRorSettings): Unit = { val core = factory() .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider ) .runSyncUnsafe() - inside(core) { case Right(Core(_, RorConfig(_, _, _, None))) => } + inside(core) { case Right(Core(_, RorDependencies(_, _, _), None)) => } } - private def assertSettings(config: RawRorConfig, expectedConfigs: NonEmptyList[AuditSink]): Unit = { + private def assertSettings(settings: RawRorSettings, expectedAuditSinks: NonEmptyList[AuditSink]): Unit = { val core = factory() .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider ) .runSyncUnsafe() - inside(core) { case Right(Core(_, RorConfig(_, _, _, Some(settings)))) => - settings.auditSinks should be(expectedConfigs) + inside(core) { case Right(Core(_, RorDependencies(_, _, _), Some(settings))) => + settings.auditSinks should be(expectedAuditSinks) } } - private def assertIndexBasedAuditSinkSettingsPresent[EXPECTED_SERIALIZER: ClassTag](config: RawRorConfig, + private def assertIndexBasedAuditSinkSettingsPresent[EXPECTED_SERIALIZER: ClassTag](settings: RawRorSettings, expectedIndexName: NonEmptyString, expectedAuditCluster: AuditCluster) = { val core = factory() .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider ) .runSyncUnsafe() - inside(core) { case Right(Core(_, RorConfig(_, _, _, Some(auditingSettings)))) => + inside(core) { case Right(Core(_, RorDependencies(_, _, _), Some(auditingSettings))) => auditingSettings.auditSinks.size should be(1) val headSink = auditingSettings.auditSinks.head @@ -2099,20 +2102,20 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } - private def assertDataStreamAuditSinkSettingsPresent[EXPECTED_SERIALIZER: ClassTag](config: RawRorConfig, + private def assertDataStreamAuditSinkSettingsPresent[EXPECTED_SERIALIZER: ClassTag](settings: RawRorSettings, expectedDataStreamName: NonEmptyString, expectedAuditCluster: AuditCluster, esVersion: EsVersion = defaultEsVersionForTests) = { val core = factory(esVersion) .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider ) .runSyncUnsafe() - inside(core) { case Right(Core(_, RorConfig(_, _, _, Some(auditingSettings)))) => + inside(core) { case Right(Core(_, RorDependencies(_, _, _), Some(auditingSettings))) => auditingSettings.auditSinks.size should be(1) val headSink = auditingSettings.auditSinks.head @@ -2128,12 +2131,12 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } - private def serializer(config: RawRorConfig, + private def serializer(settings: RawRorSettings, esVersion: EsVersion = defaultEsVersionForTests): AuditLogSerializer = { val core = factory(esVersion) .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider @@ -2141,7 +2144,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { .runSyncUnsafe() core match { - case Right(Core(_, RorConfig(_, _, _, Some(auditingSettings)))) => + case Right(Core(_, _, Some(auditingSettings))) => val headSink = auditingSettings.auditSinks.head val headSinkConfig = headSink.asInstanceOf[AuditSink.Enabled].config headSinkConfig.logSerializer @@ -2150,18 +2153,18 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } - private def assertLogBasedAuditSinkSettingsPresent[EXPECTED_SERIALIZER: ClassTag](config: RawRorConfig, + private def assertLogBasedAuditSinkSettingsPresent[EXPECTED_SERIALIZER: ClassTag](settings: RawRorSettings, expectedLoggerName: NonEmptyString) = { val core = factory() .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider ) .runSyncUnsafe() - inside(core) { case Right(Core(_, RorConfig(_, _, _, Some(auditingSettings)))) => + inside(core) { case Right(Core(_, RorDependencies(_, _, _), Some(auditingSettings))) => auditingSettings.auditSinks.size should be(1) val headSink = auditingSettings.auditSinks.head @@ -2176,13 +2179,13 @@ class AuditSettingsTests extends AnyWordSpec with Inside { } } - private def assertInvalidSettings(config: RawRorConfig, + private def assertInvalidSettings(settings: RawRorSettings, expectedErrorMessage: String, esVersion: EsVersion = defaultEsVersionForTests): Unit = { val core = factory(esVersion) .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), MockHttpClientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider @@ -2209,6 +2212,7 @@ class AuditSettingsTests extends AnyWordSpec with Inside { parser.parse(jsonObject.toString(0)).left.map(_.getMessage) } + } private class TestEnvironmentAwareAuditLogSerializer extends EnvironmentAwareAuditLogSerializer { @@ -2264,5 +2268,5 @@ private class DummyAuditRequestContext(override val loggedInUserName: Option[Str override def generalAuditEvents: JSONObject = new JSONObject - override def auditEnvironmentContext: AuditEnvironmentContext = testAuditEnvironmentContext + override def auditEnvironmentContext: AuditEnvironmentContext = new AuditEnvironmentContextBasedOnEsNodeSettings(testEsNodeSettings) } diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/CoreFactoryTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/CoreFactoryTests.scala index 2982cc6393..3c5d58183d 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/CoreFactoryTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/CoreFactoryTests.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.unit.acl.factory +import better.files.File import cats.data.NonEmptyList import eu.timepit.refined.types.string.NonEmptyString import monix.execution.Scheduler.Implicits.global @@ -23,30 +24,33 @@ import org.scalamock.scalatest.MockFactory import org.scalatest.Inside import org.scalatest.matchers.should.Matchers.* import org.scalatest.wordspec.AnyWordSpec +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.EnabledAccessControlList import tech.beshu.ror.accesscontrol.blocks.Block import tech.beshu.ror.accesscontrol.blocks.mocks.NoOpMocksProvider -import tech.beshu.ror.accesscontrol.domain.{Header, IndexName, RorConfigurationIndex} +import tech.beshu.ror.accesscontrol.domain.{Header, IndexName, RorSettingsIndex} import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} -import tech.beshu.ror.accesscontrol.factory.{Core, CoreFactory, HttpClientsFactory, RawRorConfigBasedCoreFactory} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.{Core, CoreFactory, HttpClientsFactory, RawRorSettingsBasedCoreFactory} +import tech.beshu.ror.es.EsEnv import tech.beshu.ror.mocks.{MockHttpClientsFactory, MockHttpClientsFactoryWithFixedHttpClient, MockLdapConnectionPoolProvider} +import tech.beshu.ror.settings.ror.RawRorSettings import tech.beshu.ror.syntax.* import tech.beshu.ror.utils.TestsUtils.* class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { private val factory: CoreFactory = { - implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - new RawRorConfigBasedCoreFactory(defaultEsVersionForTests) + implicit val systemContext: SystemContext = SystemContext.default + val esEnv = EsEnv(File("/config"), File("/modules"), defaultEsVersionForTests, testEsNodeSettings) + new RawRorSettingsBasedCoreFactory(esEnv) } "A RorAclFactory" should { "return headers list" when { "the section is not defined" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -57,12 +61,12 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: admin:container | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) val obfuscatedHeaders = acl.toOption.get.accessControl.staticContext.obfuscatedHeaders obfuscatedHeaders shouldEqual Set(Header.Name.authorization) } "the section exists, and obfuscated header is not defined" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -74,12 +78,12 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | | obfuscated_headers: [] |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) val headers = acl.toOption.get.accessControl.staticContext.obfuscatedHeaders headers shouldBe empty } "the section exists, and obfuscated header is defined" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -92,7 +96,7 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | obfuscated_headers: | - CorpoAuth |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) val headers = acl.toOption.get.accessControl.staticContext.obfuscatedHeaders headers should have size 1 headers.head should be(Header.Name(NonEmptyString.unsafeFrom("CorpoAuth"))) @@ -100,7 +104,7 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { } "check policy" when { "allow policy set" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -116,8 +120,8 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: test:test | |""".stripMargin) - inside(createCore(config)) { - case Right(Core(acl: EnabledAccessControlList, _)) => + inside(createCore(settings)) { + case Right(Core(acl: EnabledAccessControlList, _, _)) => val firstBlock = acl.blocks.head firstBlock.name should be(Block.Name("test_block1")) firstBlock.policy should be(Block.Policy.Allow) @@ -132,7 +136,7 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { } } "forbid policy set" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -154,8 +158,8 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: test:test | |""".stripMargin) - inside(createCore(config)) { - case Right(Core(acl: EnabledAccessControlList, _)) => + inside(createCore(settings)) { + case Right(Core(acl: EnabledAccessControlList, _, _)) => val firstBlock = acl.blocks.head firstBlock.name should be(Block.Name("test_block1")) firstBlock.policy should be(Block.Policy.Forbid(None)) @@ -178,7 +182,7 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { } "return blocks level error" when { "there is no `access_control_rules` section" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -191,11 +195,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | user_id_header: "X-Auth-Token1" | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message(s"No access_control_rules section found"))))) } "there is `access_control_rules` section defined, but without any block" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -210,11 +214,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | user_id_header: "X-Auth-Token1" | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message(s"access_control_rules defined, but no block found"))))) } "two blocks has the same names" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -235,11 +239,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: admin:container | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message(s"Blocks must have unique names. Duplicates: test_block, test_block2"))))) } "block has no name" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -249,7 +253,7 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: admin:container | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(MalformedValue.fromString( """type: "allow" |auth_key: "admin:container" @@ -258,7 +262,7 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { } "block has unknown policy type" when { "simple policy format" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -269,11 +273,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: admin:container | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message("Unknown block policy type: unknown. Supported types: 'allow'(default), 'forbid'."))))) } "extended policy format" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -285,13 +289,13 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: admin:container | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message("Unknown block policy type: unknown. Supported types: 'allow'(default), 'forbid'."))))) } } "block has unknown verbosity" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -302,11 +306,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: admin:container | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message("Unknown verbosity value: unknown. Supported types: 'info'(default), 'error'."))))) } "block has authorization rule, but no authentication rule" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -327,11 +331,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | response_group_ids_json_path: "$..groups[?(@.id)].id" | |""".stripMargin) - val acl = createCore(config, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) + val acl = createCore(settings, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message("The 'test_block' block contains an authorization rule, but not an authentication rule. This does not mean anything if you don't also set some authentication rule."))))) } "block has many authentication rules" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -350,11 +354,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | user_id_header: "X-Auth-Token" | """.stripMargin) - val acl = createCore(config, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) + val acl = createCore(settings, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message("The 'test_block' block should contain only one authentication rule, but contains: [auth_key, proxy_auth]"))))) } "block uses user variable without defining authentication rule beforehand" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -363,11 +367,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | - name: test_block | uri_re: "some_@{user}" |""".stripMargin) - val acl = createCore(config, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) + val acl = createCore(settings, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message("The 'test_block' block doesn't meet requirements for defined variables. Variable used to extract user requires one of the rules defined in block to be authentication rule"))))) } "'groups' rule uses jwt variable without defining `jwt_auth` rule beforehand" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -383,13 +387,13 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | auth_key: "user2:pass" | |""".stripMargin) - val acl = createCore(config, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) + val acl = createCore(settings, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient])) acl should be(Left(NonEmptyList.one(BlocksLevelCreationError(Message("The 'test_block' block doesn't meet requirements for defined variables. JWT variables are not allowed to be used in Groups rule"))))) } } "return rule level error" when { "no rules are defined in block" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -399,11 +403,11 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | type: allow | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(RulesLevelCreationError(Message("No rules defined in block"))))) } "block has unknown rules" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -414,12 +418,12 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | unknown_rule2: value1 | |""".stripMargin) - val acl = createCore(config) + val acl = createCore(settings) acl should be(Left(NonEmptyList.one(RulesLevelCreationError(Message("Unknown rules: unknown_rule1, unknown_rule2"))))) } } - "return ACL with blocks defined in config" in { - val config = rorConfigFromUnsafe( + "return ACL with blocks defined in settings" in { + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -437,8 +441,8 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | |""".stripMargin) - inside(createCore(config)) { - case Right(Core(acl: EnabledAccessControlList, _)) => + inside(createCore(settings)) { + case Right(Core(acl: EnabledAccessControlList, _, _)) => val firstBlock = acl.blocks.head firstBlock.name should be(Block.Name("test_block1")) firstBlock.policy should be(Block.Policy.Forbid(None)) @@ -452,9 +456,9 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { secondBlock.rules should have size 1 } } - "return ACL with blocks defined in config" when { + "return ACL with blocks defined in settings" when { "each block meets requirements for variables" in { - val config = rorConfigFromUnsafe( + val settings = rorSettingsFromUnsafe( """ |readonlyrest: | @@ -468,8 +472,8 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { | uri_re: "/endpoint_@{acl:current_group}" |""".stripMargin) - inside(createCore(config, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient]))) { - case Right(Core(acl: EnabledAccessControlList, _)) => + inside(createCore(settings, new MockHttpClientsFactoryWithFixedHttpClient(mock[HttpClient]))) { + case Right(Core(acl: EnabledAccessControlList, _, _)) => val firstBlock = acl.blocks.head firstBlock.name should be(Block.Name("test_block1")) firstBlock.rules should have size 2 @@ -482,12 +486,12 @@ class CoreFactoryTests extends AnyWordSpec with Inside with MockFactory { } } - private def createCore(config: RawRorConfig, + private def createCore(settings: RawRorSettings, clientsFactory: HttpClientsFactory = MockHttpClientsFactory) = { factory .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), clientsFactory, MockLdapConnectionPoolProvider, NoOpMocksProvider diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/ImpersonationWarningsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/ImpersonationWarningsTests.scala index 277b8894ef..3505c25d3f 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/ImpersonationWarningsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/ImpersonationWarningsTests.scala @@ -16,30 +16,34 @@ */ package tech.beshu.ror.unit.acl.factory +import better.files.File import eu.timepit.refined.types.string.NonEmptyString import monix.execution.Scheduler.Implicits.global import org.scalatest.Inside import org.scalatest.matchers.should.Matchers.* import org.scalatest.wordspec.AnyWordSpec +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.LdapService import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationService, ExternalAuthorizationService} import tech.beshu.ror.accesscontrol.blocks.mocks.{MocksProvider, NoOpMocksProvider} import tech.beshu.ror.accesscontrol.blocks.rules.Rule import tech.beshu.ror.accesscontrol.blocks.{Block, ImpersonationWarning} -import tech.beshu.ror.accesscontrol.domain.{IndexName, RequestId, RorConfigurationIndex} -import tech.beshu.ror.accesscontrol.factory.{CoreFactory, HttpClientsFactory, RawRorConfigBasedCoreFactory} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} +import tech.beshu.ror.accesscontrol.domain.{IndexName, RequestId, RorSettingsIndex} +import tech.beshu.ror.accesscontrol.factory.{CoreFactory, HttpClientsFactory, RawRorSettingsBasedCoreFactory} +import tech.beshu.ror.es.EsEnv import tech.beshu.ror.mocks.MockHttpClientsFactory +import tech.beshu.ror.settings.ror.RawRorSettings import tech.beshu.ror.syntax.* import tech.beshu.ror.utils.SingletonLdapContainers import tech.beshu.ror.utils.TestsUtils.* class ImpersonationWarningsTests extends AnyWordSpec with Inside { - "ROR config impersonation warnings" should { + + "ROR settings impersonation warnings" should { "return no warnings" when { "auth key block" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -47,10 +51,10 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | auth_key: admin:container |""".stripMargin - impersonationWarningsReader(config).read() should be(noWarnings) + impersonationWarningsReader(settings).read() should be(noWarnings) } "rules with hashed only password" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -66,14 +70,14 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | auth_key: admin:container |""".stripMargin - impersonationWarningsReader(config).read() should be(noWarnings) + impersonationWarningsReader(settings).read() should be(noWarnings) } "ldap service is mocked" in { val mocksProvider = mocksProviderForLdapFrom( Map(LdapService.Name("ldap1") -> Map.empty) ) - val config = + val settings = s""" |readonlyrest: | @@ -105,14 +109,14 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | search_groups_base_DN: "ou=People,dc=example,dc=com" |""".stripMargin - impersonationWarningsReader(config, mocksProvider).read() should be(noWarnings) + impersonationWarningsReader(settings, mocksProvider).read() should be(noWarnings) } "external authentication service is mocked" in { val mocksProvider = mocksProviderForExternalAuthnServiceFrom( Map(ExternalAuthenticationService.Name("ext1") -> Set.empty) ) - val config = + val settings = s""" |readonlyrest: | @@ -127,14 +131,14 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | authentication_endpoint: "http://localhost:8080/auth1" |""".stripMargin - impersonationWarningsReader(config, mocksProvider).read() should be(noWarnings) + impersonationWarningsReader(settings, mocksProvider).read() should be(noWarnings) } "external authorization service is mocked" in { val mocksProvider = mocksProviderForExternalAuthzServiceFrom( Map(ExternalAuthorizationService.Name("GroupsService1") -> Map.empty) ) - val config = + val settings = """ |readonlyrest: | @@ -157,12 +161,12 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | |""".stripMargin - impersonationWarningsReader(config, mocksProvider).read() should be(noWarnings) + impersonationWarningsReader(settings, mocksProvider).read() should be(noWarnings) } } "return warnings" when { "rules with hashed username and password" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -178,7 +182,7 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | auth_key: admin:container |""".stripMargin - impersonationWarningsReader(config).read() should be(List( + impersonationWarningsReader(settings).read() should be(List( fullyHashedCredentialsWarning("test_block1", "auth_key_sha1"), fullyHashedCredentialsWarning("test_block2", "auth_key_sha256"), fullyHashedCredentialsWarning("test_block3", "auth_key_sha512"), @@ -186,7 +190,7 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { )) } "ldap service is not mocked" in { - val config = + val settings = s""" |readonlyrest: | @@ -220,14 +224,14 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { val hint = "Configure a mock of an LDAP service with ID [ldap1]" - impersonationWarningsReader(config).read() should be(List( + impersonationWarningsReader(settings).read() should be(List( notMockedServiceWarning("test_block1", "ldap_auth", "ldap1", hint), notMockedServiceWarning("test_block2", "ldap_authentication", "ldap1", hint), notMockedServiceWarning("test_block3", "ldap_authorization", "ldap1", hint), )) } "external authentication service is not mocked" in { - val config = + val settings = s""" |readonlyrest: | @@ -243,12 +247,12 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { |""".stripMargin val hint = "Configure a mock of an external authentication service with ID [ext1]" - impersonationWarningsReader(config, NoOpMocksProvider).read() should be(List( + impersonationWarningsReader(settings, NoOpMocksProvider).read() should be(List( notMockedServiceWarning("test_block1", "external_authentication", "ext1", hint) )) } "external authorization service is not mocked" in { - val config = + val settings = """ |readonlyrest: | @@ -272,13 +276,13 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { |""".stripMargin val hint = "Configure a mock of an external authorization service with ID [GroupsService1]" - impersonationWarningsReader(config, NoOpMocksProvider).read() should be(List( + impersonationWarningsReader(settings, NoOpMocksProvider).read() should be(List( notMockedServiceWarning("test_block1", "groups_provider_authorization", "GroupsService1", hint) )) } "impersonation not supported by rule" when { "jwt rule" in { - val config = + val settings = """ |readonlyrest: | @@ -294,12 +298,12 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | |""".stripMargin - impersonationWarningsReader(config, NoOpMocksProvider).read() should be(List( + impersonationWarningsReader(settings, NoOpMocksProvider).read() should be(List( impersonationNotSupportedWarning("test_block1", "jwt_auth") )) } "ror kbn auth rule" in { - val config = + val settings = """ |readonlyrest: | @@ -317,12 +321,12 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | |""".stripMargin - impersonationWarningsReader(config, NoOpMocksProvider).read() should be(List( + impersonationWarningsReader(settings, NoOpMocksProvider).read() should be(List( impersonationNotSupportedWarning("test_block1", "ror_kbn_auth") )) } "ror kbn auth rule (configured without groups)" in { - val config = + val settings = """ |readonlyrest: | @@ -338,7 +342,7 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { | |""".stripMargin - impersonationWarningsReader(config, NoOpMocksProvider).read() should be(List( + impersonationWarningsReader(settings, NoOpMocksProvider).read() should be(List( impersonationNotSupportedWarning("test_block1", "ror_kbn_auth") )) } @@ -352,7 +356,7 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { warning( blockName, ruleName, - message = "The rule contains fully hashed username and password. It doesn't support impersonation in this configuration", + message = "The rule contains fully hashed username and password. It doesn't support impersonation in this use case.", hint = s"You can use second version of the rule and use not hashed username. Like that: `$ruleName: USER_NAME:hash(PASSWORD)" ) } @@ -384,23 +388,22 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { ) } - private def impersonationWarningsReader(config: String, mocksProvider: MocksProvider = NoOpMocksProvider) = { - val rorConfig = rorConfigFromUnsafe(config) - inside(createCore(config = rorConfig, mocksProvider = mocksProvider)) { - case Right(core) => - core.rorConfig.impersonationWarningsReader + private def impersonationWarningsReader(settingsString: String, mocksProvider: MocksProvider = NoOpMocksProvider) = { + val settings = rorSettingsFromUnsafe(settingsString) + inside(createCore(settings, mocksProvider = mocksProvider)) { + case Right(core) => core.dependencies.impersonationWarningsReader } } private implicit val dummyRequestID: RequestId = RequestId("dummy") - private def createCore(config: RawRorConfig, + private def createCore(settings: RawRorSettings, clientsFactory: HttpClientsFactory = MockHttpClientsFactory, mocksProvider: MocksProvider) = { factory .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), clientsFactory, new UnboundidLdapConnectionPoolProvider(), mocksProvider @@ -409,7 +412,8 @@ class ImpersonationWarningsTests extends AnyWordSpec with Inside { } private val factory: CoreFactory = { - implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - new RawRorConfigBasedCoreFactory(defaultEsVersionForTests) + implicit val systemContext: SystemContext = SystemContext.default + val esEnv = EsEnv(File("/config"), File("/modules"), defaultEsVersionForTests, testEsNodeSettings) + new RawRorSettingsBasedCoreFactory(esEnv) } } diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/LocalUsersTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/LocalUsersTest.scala index e931d05d60..77539529d3 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/LocalUsersTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/LocalUsersTest.scala @@ -16,26 +16,29 @@ */ package tech.beshu.ror.unit.acl.factory +import better.files.File import monix.execution.Scheduler.Implicits.global import org.scalatest.Inside import org.scalatest.matchers.should.Matchers.* import org.scalatest.wordspec.AnyWordSpec +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UnboundidLdapConnectionPoolProvider import tech.beshu.ror.accesscontrol.blocks.mocks.NoOpMocksProvider -import tech.beshu.ror.accesscontrol.domain.{IndexName, LocalUsers, RorConfigurationIndex, User} -import tech.beshu.ror.accesscontrol.factory.{HttpClientsFactory, RawRorConfigBasedCoreFactory} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig} +import tech.beshu.ror.accesscontrol.domain.{IndexName, LocalUsers, RorSettingsIndex, User} +import tech.beshu.ror.accesscontrol.factory.{HttpClientsFactory, RawRorSettingsBasedCoreFactory} +import tech.beshu.ror.es.EsEnv import tech.beshu.ror.mocks.{MockHttpClientsFactory, MockLdapConnectionPoolProvider} +import tech.beshu.ror.settings.ror.RawRorSettings import tech.beshu.ror.syntax.* import tech.beshu.ror.utils.SingletonLdapContainers import tech.beshu.ror.utils.TestsUtils.* class LocalUsersTest extends AnyWordSpec with Inside { - "ROR config local users" should { + "ROR setting local users" should { "return info that all users are resolved" when { "auth key block" in { - assertLocalUsersFromConfig( + assertLocalUsersFromSettings( s""" |readonlyrest: | access_control_rules: @@ -46,7 +49,7 @@ class LocalUsersTest extends AnyWordSpec with Inside { ) } "username used in two rules" in { - assertLocalUsersFromConfig( + assertLocalUsersFromSettings( s""" |readonlyrest: | access_control_rules: @@ -59,7 +62,7 @@ class LocalUsersTest extends AnyWordSpec with Inside { ) } "different users defined in rules" in { - assertLocalUsersFromConfig( + assertLocalUsersFromSettings( s""" |readonlyrest: | access_control_rules: @@ -72,7 +75,7 @@ class LocalUsersTest extends AnyWordSpec with Inside { ) } "hashed is only password" in { - assertLocalUsersFromConfig( + assertLocalUsersFromSettings( s""" |readonlyrest: | access_control_rules: @@ -85,7 +88,7 @@ class LocalUsersTest extends AnyWordSpec with Inside { ) } "'proxy_auth' rule" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -131,14 +134,14 @@ class LocalUsersTest extends AnyWordSpec with Inside { | connection_pool_size: 10 # default 30 |""".stripMargin - assertLocalUsersFromConfig( - config, + assertLocalUsersFromSettings( + settings, allUsersResolved(Set(User.Id("admin"), User.Id("dev"))) ) } "users section defined without wildcard patterns" when { "auth_key rules used" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -159,12 +162,12 @@ class LocalUsersTest extends AnyWordSpec with Inside { | auth_key: "user4:pass" |""".stripMargin - assertLocalUsersFromConfig(config, allUsersResolved(Set( + assertLocalUsersFromSettings(settings, allUsersResolved(Set( User.Id("user1"), User.Id("user2"), User.Id("user4"), User.Id("admin") ))) } "ldap_authentication rule used" in { - val config = + val settings = rorSettingsFromUnsafe { s""" |readonlyrest: | access_control_rules: @@ -198,17 +201,17 @@ class LocalUsersTest extends AnyWordSpec with Inside { | groups: | search_groups_base_DN: "ou=People,dc=example,dc=com" |""".stripMargin + } - val rorConfig = rorConfigFromUnsafe(config) - inside(createCore(rorConfig, new UnboundidLdapConnectionPoolProvider())) { + inside(createCore(settings, new UnboundidLdapConnectionPoolProvider())) { case Right(core) => - core.rorConfig.localUsers should be(allUsersResolved(Set( + core.dependencies.localUsers should be(allUsersResolved(Set( User.Id("admin"), User.Id("cartman"), User.Id("Bìlbö Bággįnš"), User.Id("bong"), User.Id("morgan") ))) } } "ror_kbn_auth rule used" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -249,10 +252,10 @@ class LocalUsersTest extends AnyWordSpec with Inside { | signature_key: "1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890" |""".stripMargin - val rorConfig = rorConfigFromUnsafe(config) - inside(createCore(rorConfig, new UnboundidLdapConnectionPoolProvider())) { + val rorSettings = rorSettingsFromUnsafe(settings) + inside(createCore(rorSettings, new UnboundidLdapConnectionPoolProvider())) { case Right(core) => - core.rorConfig.localUsers should be(allUsersResolved(Set( + core.dependencies.localUsers should be(allUsersResolved(Set( User.Id("admin"), User.Id("cartman"), User.Id("Bìlbö Bággįnš"), User.Id("bong"), User.Id("morgan") ))) case Left(error) => @@ -260,7 +263,7 @@ class LocalUsersTest extends AnyWordSpec with Inside { } } "ror_kbn_authentication rule used" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -301,10 +304,10 @@ class LocalUsersTest extends AnyWordSpec with Inside { | signature_key: "1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890" |""".stripMargin - val rorConfig = rorConfigFromUnsafe(config) - inside(createCore(rorConfig, new UnboundidLdapConnectionPoolProvider())) { + val rorSettings = rorSettingsFromUnsafe(settings) + inside(createCore(rorSettings, new UnboundidLdapConnectionPoolProvider())) { case Right(core) => - core.rorConfig.localUsers should be(allUsersResolved(Set( + core.dependencies.localUsers should be(allUsersResolved(Set( User.Id("admin"), User.Id("cartman"), User.Id("Bìlbö Bággįnš"), User.Id("bong"), User.Id("morgan") ))) case Left(error) => @@ -313,7 +316,7 @@ class LocalUsersTest extends AnyWordSpec with Inside { } } "impersonators section defined with users" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -331,14 +334,14 @@ class LocalUsersTest extends AnyWordSpec with Inside { | auth_key: devAdmin3:pass | users: ["*", "user*"] |""".stripMargin - assertLocalUsersFromConfig(config, expected = allUsersResolved(Set( + assertLocalUsersFromSettings(settings, expected = allUsersResolved(Set( User.Id("admin"), User.Id("user1"), User.Id("user2"), User.Id("user3") ))) } } - "return info that unknown users in config" when { + "return info that unknown users in settings" when { "hashed username and password" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -349,10 +352,10 @@ class LocalUsersTest extends AnyWordSpec with Inside { | - name: test_block3 | auth_key: admin:container |""".stripMargin - assertLocalUsersFromConfig(config, expected = withUnknownUsers(Set(User.Id("admin")))) + assertLocalUsersFromSettings(settings, expected = withUnknownUsers(Set(User.Id("admin")))) } "there is some user with hashed credentials" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -361,10 +364,10 @@ class LocalUsersTest extends AnyWordSpec with Inside { | - name: test_block2 | auth_key: admin:container |""".stripMargin - assertLocalUsersFromConfig(config, withUnknownUsers(Set(User.Id("admin")))) + assertLocalUsersFromSettings(settings, withUnknownUsers(Set(User.Id("admin")))) } "users section defined with wildcard patterns" in { - val config = + val settings = s""" |readonlyrest: | access_control_rules: @@ -388,7 +391,7 @@ class LocalUsersTest extends AnyWordSpec with Inside { | groups: ["group5", "group6"] | auth_key_sha1: "d27aaf7fa3c1603948bb29b7339f2559dc02019a" |""".stripMargin - assertLocalUsersFromConfig(config, expected = withUnknownUsers(Set( + assertLocalUsersFromSettings(settings, expected = withUnknownUsers(Set( User.Id("admin"), User.Id("user1"), User.Id("user2"), User.Id("user4") ))) } @@ -399,21 +402,21 @@ class LocalUsersTest extends AnyWordSpec with Inside { private def allUsersResolved(users: Set[User.Id]) = LocalUsers(users, unknownUsers = false) - private def assertLocalUsersFromConfig(config: String, expected: LocalUsers) = { - val rorConfig = rorConfigFromUnsafe(config) - inside(createCore(rorConfig)) { + private def assertLocalUsersFromSettings(settingsString: String, expected: LocalUsers) = { + val settings = rorSettingsFromUnsafe(settingsString) + inside(createCore(settings)) { case Right(core) => - core.rorConfig.localUsers should be(expected) + core.dependencies.localUsers should be(expected) } } - private def createCore(config: RawRorConfig, + private def createCore(settings: RawRorSettings, ldapConnectionPoolProvider: UnboundidLdapConnectionPoolProvider = MockLdapConnectionPoolProvider, clientsFactory: HttpClientsFactory = MockHttpClientsFactory) = { factory .createCoreFrom( - config, - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settings, + RorSettingsIndex(IndexName.Full(".readonlyrest")), clientsFactory, ldapConnectionPoolProvider, NoOpMocksProvider @@ -422,8 +425,9 @@ class LocalUsersTest extends AnyWordSpec with Inside { } private val factory = { - implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - new RawRorConfigBasedCoreFactory(defaultEsVersionForTests) + implicit val systemContext: SystemContext = SystemContext.default + val esEnv = EsEnv(File("/config"), File("/modules"), defaultEsVersionForTests, testEsNodeSettings) + new RawRorSettingsBasedCoreFactory(esEnv) } } diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/BaseDecoderTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/BaseDecoderTest.scala index b650ace46b..0e55e98a09 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/BaseDecoderTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/BaseDecoderTest.scala @@ -22,7 +22,7 @@ import cats.implicits.* import io.circe.DecodingFailure import org.scalatest.Inside import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError import tech.beshu.ror.accesscontrol.utils.ADecoder import tech.beshu.ror.accesscontrol.utils.CirceOps.DecodingFailureOps import tech.beshu.ror.utils.TestsUtils diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/GlobalSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/GlobalSettingsTests.scala index ae78d0a323..698cbafe07 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/GlobalSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/GlobalSettingsTests.scala @@ -18,18 +18,18 @@ package tech.beshu.ror.unit.acl.factory.decoders.definitions import eu.timepit.refined.types.string.NonEmptyString import org.scalatest.matchers.should.Matchers.* -import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, IndexName, RorConfigurationIndex} +import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, IndexName, RorSettingsIndex} import tech.beshu.ror.accesscontrol.factory.GlobalSettings import tech.beshu.ror.accesscontrol.factory.GlobalSettings.FlsEngine -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.GeneralReadonlyrestSettingsError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.GeneralReadonlyrestSettingsError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.GlobalStaticSettingsDecoder import tech.beshu.ror.accesscontrol.utils.{SyncDecoder, SyncDecoderCreator} class GlobalSettingsTests extends BaseDecoderTest(GlobalSettingsTests.decoder) { - "A global settings should be able to be loaded from config (in the 'readonlyrest.global_settings' section level)" when { + "A global settings should be able to be loaded from settings (in the 'readonlyrest.global_settings' section level)" when { "'prompt_for_basic_auth'" should { "be decoded with success" when { "enabled" in { @@ -39,8 +39,8 @@ class GlobalSettingsTests | global_settings: | prompt_for_basic_auth: true """.stripMargin, - assertion = config => - config.showBasicAuthPrompt should be(true) + assertion = settings => + settings.showBasicAuthPrompt should be(true) ) } "disabled" in { @@ -50,15 +50,15 @@ class GlobalSettingsTests | global_settings: | prompt_for_basic_auth: false """.stripMargin, - assertion = config => - config.showBasicAuthPrompt should be(false) + assertion = settings => + settings.showBasicAuthPrompt should be(false) ) } "not defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.showBasicAuthPrompt should be(false) + assertion = settings => + settings.showBasicAuthPrompt should be(false) ) } } @@ -72,15 +72,15 @@ class GlobalSettingsTests | global_settings: | response_if_req_forbidden: custom_forbidden_response """.stripMargin, - assertion = config => - config.forbiddenRequestMessage should be("custom_forbidden_response") + assertion = settings => + settings.forbiddenRequestMessage should be("custom_forbidden_response") ) } "not defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.forbiddenRequestMessage should be("Forbidden by ReadonlyREST") + assertion = settings => + settings.forbiddenRequestMessage should be("Forbidden by ReadonlyREST") ) } } @@ -94,8 +94,8 @@ class GlobalSettingsTests | global_settings: | fls_engine: lucene """.stripMargin, - assertion = config => - config.flsEngine should be(FlsEngine.Lucene) + assertion = settings => + settings.flsEngine should be(FlsEngine.Lucene) ) } "es_with_lucene" in { @@ -105,8 +105,8 @@ class GlobalSettingsTests | global_settings: | fls_engine: es_with_lucene """.stripMargin, - assertion = config => - config.flsEngine should be(FlsEngine.ESWithLucene) + assertion = settings => + settings.flsEngine should be(FlsEngine.ESWithLucene) ) } "es" in { @@ -116,15 +116,15 @@ class GlobalSettingsTests | global_settings: | fls_engine: es """.stripMargin, - assertion = config => - config.flsEngine should be(FlsEngine.ES) + assertion = settings => + settings.flsEngine should be(FlsEngine.ES) ) } "not defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.flsEngine should be(FlsEngine.ESWithLucene) + assertion = settings => + settings.flsEngine should be(FlsEngine.ESWithLucene) ) } } @@ -154,8 +154,8 @@ class GlobalSettingsTests | global_settings: | username_case_sensitivity: case_sensitive """.stripMargin, - assertion = config => - config.userIdCaseSensitivity should be(CaseSensitivity.Enabled) + assertion = settings => + settings.userIdCaseSensitivity should be(CaseSensitivity.Enabled) ) } "case insensitive" in { @@ -165,15 +165,15 @@ class GlobalSettingsTests | global_settings: | username_case_sensitivity: case_insensitive """.stripMargin, - assertion = config => - config.userIdCaseSensitivity should be(CaseSensitivity.Disabled) + assertion = settings => + settings.userIdCaseSensitivity should be(CaseSensitivity.Disabled) ) } "no defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.userIdCaseSensitivity should be(CaseSensitivity.Enabled) + assertion = settings => + settings.userIdCaseSensitivity should be(CaseSensitivity.Enabled) ) } } @@ -203,8 +203,8 @@ class GlobalSettingsTests | global_settings: | users_section_duplicate_usernames_detection: true """.stripMargin, - assertion = config => - config.usersDefinitionDuplicateUsernamesValidationEnabled should be(true) + assertion = settings => + settings.usersDefinitionDuplicateUsernamesValidationEnabled should be(true) ) } "disabled" in { @@ -214,22 +214,22 @@ class GlobalSettingsTests | global_settings: | users_section_duplicate_usernames_detection: false """.stripMargin, - assertion = config => - config.usersDefinitionDuplicateUsernamesValidationEnabled should be(false) + assertion = settings => + settings.usersDefinitionDuplicateUsernamesValidationEnabled should be(false) ) } "no defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.usersDefinitionDuplicateUsernamesValidationEnabled should be(true) + assertion = settings => + settings.usersDefinitionDuplicateUsernamesValidationEnabled should be(true) ) } } } } - "A global settings should be able to be loaded from config (in the 'readonlyrest' section level)" when { + "A global settings should be able to be loaded from settings (in the 'readonlyrest' section level)" when { "'prompt_for_basic_auth'" should { "be decoded with success" when { "enabled" in { @@ -238,8 +238,8 @@ class GlobalSettingsTests s""" | prompt_for_basic_auth: true """.stripMargin, - assertion = config => - config.showBasicAuthPrompt should be(true) + assertion = settings => + settings.showBasicAuthPrompt should be(true) ) } "disabled" in { @@ -248,15 +248,15 @@ class GlobalSettingsTests s""" | prompt_for_basic_auth: false """.stripMargin, - assertion = config => - config.showBasicAuthPrompt should be(false) + assertion = settings => + settings.showBasicAuthPrompt should be(false) ) } "not defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.showBasicAuthPrompt should be(false) + assertion = settings => + settings.showBasicAuthPrompt should be(false) ) } } @@ -269,15 +269,15 @@ class GlobalSettingsTests s""" | response_if_req_forbidden: custom_forbidden_response """.stripMargin, - assertion = config => - config.forbiddenRequestMessage should be("custom_forbidden_response") + assertion = settings => + settings.forbiddenRequestMessage should be("custom_forbidden_response") ) } "not defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.forbiddenRequestMessage should be("Forbidden by ReadonlyREST") + assertion = settings => + settings.forbiddenRequestMessage should be("Forbidden by ReadonlyREST") ) } } @@ -290,8 +290,8 @@ class GlobalSettingsTests s""" | fls_engine: lucene """.stripMargin, - assertion = config => - config.flsEngine should be(FlsEngine.Lucene) + assertion = settings => + settings.flsEngine should be(FlsEngine.Lucene) ) } "es_with_lucene" in { @@ -300,8 +300,8 @@ class GlobalSettingsTests s""" | fls_engine: es_with_lucene """.stripMargin, - assertion = config => - config.flsEngine should be(FlsEngine.ESWithLucene) + assertion = settings => + settings.flsEngine should be(FlsEngine.ESWithLucene) ) } "es" in { @@ -310,15 +310,15 @@ class GlobalSettingsTests s""" | fls_engine: es """.stripMargin, - assertion = config => - config.flsEngine should be(FlsEngine.ES) + assertion = settings => + settings.flsEngine should be(FlsEngine.ES) ) } "not defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.flsEngine should be(FlsEngine.ESWithLucene) + assertion = settings => + settings.flsEngine should be(FlsEngine.ESWithLucene) ) } } @@ -346,8 +346,8 @@ class GlobalSettingsTests s""" | username_case_sensitivity: case_sensitive """.stripMargin, - assertion = config => - config.userIdCaseSensitivity should be(CaseSensitivity.Enabled) + assertion = settings => + settings.userIdCaseSensitivity should be(CaseSensitivity.Enabled) ) } "case insensitive" in { @@ -356,15 +356,15 @@ class GlobalSettingsTests s""" | username_case_sensitivity: case_insensitive """.stripMargin, - assertion = config => - config.userIdCaseSensitivity should be(CaseSensitivity.Disabled) + assertion = settings => + settings.userIdCaseSensitivity should be(CaseSensitivity.Disabled) ) } "no defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.userIdCaseSensitivity should be(CaseSensitivity.Enabled) + assertion = settings => + settings.userIdCaseSensitivity should be(CaseSensitivity.Enabled) ) } } @@ -392,8 +392,8 @@ class GlobalSettingsTests s""" | users_section_duplicate_usernames_detection: true """.stripMargin, - assertion = config => - config.usersDefinitionDuplicateUsernamesValidationEnabled should be(true) + assertion = settings => + settings.usersDefinitionDuplicateUsernamesValidationEnabled should be(true) ) } "disabled" in { @@ -402,15 +402,15 @@ class GlobalSettingsTests s""" | users_section_duplicate_usernames_detection: false """.stripMargin, - assertion = config => - config.usersDefinitionDuplicateUsernamesValidationEnabled should be(false) + assertion = settings => + settings.usersDefinitionDuplicateUsernamesValidationEnabled should be(false) ) } "not defined" in { assertDecodingSuccess( yaml = noCustomSettingsYaml, - assertion = config => - config.usersDefinitionDuplicateUsernamesValidationEnabled should be(true) + assertion = settings => + settings.usersDefinitionDuplicateUsernamesValidationEnabled should be(true) ) } } @@ -504,6 +504,6 @@ class GlobalSettingsTests private object GlobalSettingsTests { val decoder: SyncDecoder[GlobalSettings] = SyncDecoderCreator.from( - GlobalStaticSettingsDecoder.instance(RorConfigurationIndex(IndexName.Full(NonEmptyString.unsafeFrom(".readonlyrest")))) + GlobalStaticSettingsDecoder.instance(RorSettingsIndex(IndexName.Full(NonEmptyString.unsafeFrom(".readonlyrest")))) ) } diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/ImpersonationSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/ImpersonationSettingsTests.scala index 948ad463ce..c6724ad8a3 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/ImpersonationSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/ImpersonationSettingsTests.scala @@ -22,11 +22,11 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationSe import tech.beshu.ror.accesscontrol.blocks.mocks.NoOpMocksProvider import tech.beshu.ror.accesscontrol.blocks.rules.auth.{AuthKeyRule, AuthKeySha1Rule} import tech.beshu.ror.accesscontrol.domain.User.UserIdPattern -import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, RorConfigurationIndex, User, UserIdPatterns} +import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, RorSettingsIndex, User, UserIdPatterns} import tech.beshu.ror.accesscontrol.factory.GlobalSettings import tech.beshu.ror.accesscontrol.factory.GlobalSettings.FlsEngine -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.definitions.{Definitions, ImpersonationDefinitionsDecoderCreator} import tech.beshu.ror.utils.TestsUtils.* import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList @@ -37,7 +37,7 @@ class ImpersonationSettingsTests extends BaseDecoderTest( showBasicAuthPrompt = true, forbiddenRequestMessage = "Forbidden by ReadonlyREST", flsEngine = FlsEngine.ES, - configurationIndex = RorConfigurationIndex(fullIndexName(".readonlyrest")), + settingsIndex = RorSettingsIndex(fullIndexName(".readonlyrest")), userIdCaseSensitivity = CaseSensitivity.Enabled, usersDefinitionDuplicateUsernamesValidationEnabled = true ), @@ -49,7 +49,7 @@ class ImpersonationSettingsTests extends BaseDecoderTest( ) { "An impersonation definition" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one impersonator is defined" which { "using auth key as authentication method" in { assertDecodingSuccess( @@ -110,7 +110,7 @@ class ImpersonationSettingsTests extends BaseDecoderTest( ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "impersonation section is empty" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/LdapServicesSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/LdapServicesSettingsTests.scala index a55cc2aa3a..02d5dafeaa 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/LdapServicesSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/LdapServicesSettingsTests.scala @@ -30,8 +30,8 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.* import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserGroupsSearchFilterConfig.UserGroupsSearchMode.* import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserSearchFilterConfig.UserIdAttribute import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.UserSearchFilterConfig.UserIdAttribute.CustomAttribute -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} import tech.beshu.ror.accesscontrol.factory.decoders.definitions.LdapServicesDecoder import tech.beshu.ror.utils.DurationOps.RefinedDurationOps import tech.beshu.ror.utils.RefinedUtils.* @@ -74,7 +74,7 @@ class LdapServicesSettingsTests private(ldapConnectionPoolProvider: UnboundidLda ) "An LdapService" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one LDAP service is declared (without server_side_groups_filtering)" in { assertDecodingSuccess( yamls = NonEmptyList.of( @@ -1148,7 +1148,7 @@ class LdapServicesSettingsTests private(ldapConnectionPoolProvider: UnboundidLda | ldaps: | - name: ldap1 | server_discovery: - | dns_url: "dns://localhost:${ldapWithDnsContainer.dnsPort}" + | dns_url: "dns://${ldapWithDnsContainer.dnsHost}:${ldapWithDnsContainer.dnsPort}" | ttl: "3 hours" | ssl_enabled: false | ssl_trust_all_certs: true @@ -1708,7 +1708,7 @@ class LdapServicesSettingsTests private(ldapConnectionPoolProvider: UnboundidLda } ) } - "groups related param is malformed when deprecated config format is used" in { + "groups related param is malformed when deprecated settings format is used" in { assertDecodingSuccess( yamls = NonEmptyList.of( // empty search_groups_base_DN which is required @@ -1781,8 +1781,8 @@ class LdapServicesSettingsTests private(ldapConnectionPoolProvider: UnboundidLda ) } } - "not be able to be loaded from config" when { - "circuit breaker config is malformed" in { + "not be able to be loaded from settings" when { + "circuit breaker settings are malformed" in { assertDecodingFailure( yaml = s""" diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/VariableTransformationAliasesTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/VariableTransformationAliasesTests.scala index 14465716b4..5ffcd35a0e 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/VariableTransformationAliasesTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/definitions/VariableTransformationAliasesTests.scala @@ -19,8 +19,8 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.variables.transformation.SupportedVariablesFunctions import tech.beshu.ror.accesscontrol.blocks.variables.transformation.domain.Function.{FunctionChain, ReplaceFirst} import tech.beshu.ror.accesscontrol.blocks.variables.transformation.domain.FunctionName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.DefinitionsLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message import tech.beshu.ror.accesscontrol.factory.decoders.definitions.VariableTransformationAliasesDefinitionsDecoder import tech.beshu.ror.utils.TestsUtils.unsafeNes @@ -28,7 +28,7 @@ class VariableTransformationAliasesTests extends BaseDecoderTest(VariableTransformationAliasesDefinitionsDecoder.create(SupportedVariablesFunctions.default)) { "A variable transformation aliases definition" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one alias is defined" in { assertDecodingSuccess( yaml = @@ -99,7 +99,7 @@ class VariableTransformationAliasesTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "aliases section is empty" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/BaseRuleSettingsDecoderTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/BaseRuleSettingsDecoderTest.scala index c2332d1965..65f7f35220 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/BaseRuleSettingsDecoderTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/BaseRuleSettingsDecoderTest.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.unit.acl.factory.decoders.rules +import better.files.File import cats.data.NonEmptyList import monix.execution.Scheduler.Implicits.global import org.scalatest.matchers.should.Matchers.* @@ -26,10 +27,11 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.* import tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations.* import tech.beshu.ror.accesscontrol.blocks.mocks.{MocksProvider, NoOpMocksProvider} import tech.beshu.ror.accesscontrol.blocks.rules.Rule -import tech.beshu.ror.accesscontrol.domain.{IndexName, RorConfigurationIndex} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.{Core, HttpClientsFactory, RawRorConfigBasedCoreFactory} -import tech.beshu.ror.configuration.EnvironmentConfig +import tech.beshu.ror.accesscontrol.domain.{IndexName, RorSettingsIndex} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.{Core, HttpClientsFactory, RawRorSettingsBasedCoreFactory} +import tech.beshu.ror.SystemContext +import tech.beshu.ror.es.EsEnv import tech.beshu.ror.mocks.MockHttpClientsFactory import tech.beshu.ror.providers.* import tech.beshu.ror.utils.TestsUtils.* @@ -48,27 +50,28 @@ abstract class BaseRuleSettingsDecoderTest[T <: Rule : ClassTag] extends AnyWord protected implicit def envVarsProvider: EnvVarsProvider = OsEnvVarsProvider - protected def factory: RawRorConfigBasedCoreFactory = { - implicit val environmentConfig: EnvironmentConfig = new EnvironmentConfig(envVarsProvider = envVarsProvider) - new RawRorConfigBasedCoreFactory(defaultEsVersionForTests) + protected def factory: RawRorSettingsBasedCoreFactory = { + implicit val systemContext: SystemContext = new SystemContext(envVarsProvider = envVarsProvider) + val esEnv = EsEnv(File("/config"), File("/modules"), defaultEsVersionForTests, testEsNodeSettings) + new RawRorSettingsBasedCoreFactory(esEnv) } def assertDecodingSuccess(yaml: String, assertion: T => Unit, - aFactory: RawRorConfigBasedCoreFactory = factory, + aFactory: RawRorSettingsBasedCoreFactory = factory, httpClientsFactory: HttpClientsFactory = MockHttpClientsFactory, mocksProvider: MocksProvider = NoOpMocksProvider): Unit = { inside( aFactory .createCoreFrom( - rorConfigFromUnsafe(yaml), - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + rorSettingsFromUnsafe(yaml), + RorSettingsIndex(IndexName.Full(".readonlyrest")), httpClientsFactory, ldapConnectionPoolProvider, mocksProvider ) .runSyncUnsafe() - ) { case Right(Core(acl: EnabledAccessControlList, _)) => + ) { case Right(Core(acl: EnabledAccessControlList, _, _)) => val rule = acl.blocks.head.rules.collect { case r: T => r }.headOption .getOrElse(throw new IllegalStateException("There was no expected rule in decoding result")) rule shouldBe a[T] @@ -78,14 +81,14 @@ abstract class BaseRuleSettingsDecoderTest[T <: Rule : ClassTag] extends AnyWord def assertDecodingFailure(yaml: String, assertion: NonEmptyList[CoreCreationError] => Unit, - aFactory: RawRorConfigBasedCoreFactory = factory, + aFactory: RawRorSettingsBasedCoreFactory = factory, httpClientsFactory: HttpClientsFactory = MockHttpClientsFactory, mocksProvider: MocksProvider = NoOpMocksProvider): Unit = { inside( aFactory .createCoreFrom( - rorConfigFromUnsafe(yaml), - RorConfigurationIndex(IndexName.Full(".readonlyrest")), + rorSettingsFromUnsafe(yaml), + RorSettingsIndex(IndexName.Full(".readonlyrest")), httpClientsFactory, ldapConnectionPoolProvider, mocksProvider diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ApiKeysRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ApiKeysRuleSettingsTests.scala index b944587e69..d5e9090212 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ApiKeysRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ApiKeysRuleSettingsTests.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.auth import cats.data.NonEmptySet import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.http.ApiKeysRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -28,7 +28,7 @@ import tech.beshu.ror.utils.TestsUtils.* class ApiKeysRuleSettingsTests extends BaseRuleSettingsDecoderTest[ApiKeysRule] { "An ApiKeysRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one api key is defined" in { assertDecodingSuccess( yaml = @@ -64,7 +64,7 @@ class ApiKeysRuleSettingsTests extends BaseRuleSettingsDecoderTest[ApiKeysRule] ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no api key is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeyPBKDF2WithHmacSHA512RuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeyPBKDF2WithHmacSHA512RuleSettingsTests.scala index 95c3705580..fb3e100fd6 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeyPBKDF2WithHmacSHA512RuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeyPBKDF2WithHmacSHA512RuleSettingsTests.scala @@ -19,8 +19,8 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeyHashingRule.HashedCredentials import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeyPBKDF2WithHmacSHA512Rule import tech.beshu.ror.accesscontrol.domain.User -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.unsafeNes @@ -28,7 +28,7 @@ class AuthKeyPBKDF2WithHmacSHA512RuleSettingsTests extends BaseRuleSettingsDecoderTest[AuthKeyPBKDF2WithHmacSHA512Rule] { "An AuthKeyPBKDF2WithHmacSHA512Rule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "PBKDF2 auth key is defined (all hashed syntax)" in { assertDecodingSuccess( yaml = @@ -68,7 +68,7 @@ class AuthKeyPBKDF2WithHmacSHA512RuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no PBKDF2 auth key is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha1RuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha1RuleSettingsTests.scala index 9fc8ca11a5..583900ff3b 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha1RuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha1RuleSettingsTests.scala @@ -19,15 +19,15 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeyHashingRule.HashedCredentials import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeySha1Rule import tech.beshu.ror.accesscontrol.domain.User -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.unsafeNes class AuthKeySha1RuleSettingsTests extends BaseRuleSettingsDecoderTest[AuthKeySha1Rule] { "An AuthKeySha1Rule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "SHA1 auth key is defined (all hashed syntax)" in { assertDecodingSuccess( yaml = @@ -67,7 +67,7 @@ class AuthKeySha1RuleSettingsTests extends BaseRuleSettingsDecoderTest[AuthKeySh ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no SHA1 auth key is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha256RuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha256RuleSettingsTests.scala index cb6182f532..a88a942661 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha256RuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha256RuleSettingsTests.scala @@ -19,15 +19,15 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeyHashingRule.HashedCredentials import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeySha256Rule import tech.beshu.ror.accesscontrol.domain.User -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.unsafeNes class AuthKeySha256RuleSettingsTests extends BaseRuleSettingsDecoderTest[AuthKeySha256Rule] { "An AuthKeySha256Rule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "SHA256 auth key is defined (all hashed syntax)" in { assertDecodingSuccess( yaml = @@ -67,7 +67,7 @@ class AuthKeySha256RuleSettingsTests extends BaseRuleSettingsDecoderTest[AuthKey ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no SHA256 auth key is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha512RuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha512RuleSettingsTests.scala index e8ed46326c..a3cc79e3f0 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha512RuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/AuthKeySha512RuleSettingsTests.scala @@ -19,15 +19,15 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeyHashingRule.HashedCredentials import tech.beshu.ror.accesscontrol.blocks.rules.auth.AuthKeySha512Rule import tech.beshu.ror.accesscontrol.domain.User -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.unsafeNes class AuthKeySha512RuleSettingsTests extends BaseRuleSettingsDecoderTest[AuthKeySha512Rule] { "An AuthKeySha512Rule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "SHA512 auth key is defined (all hashed syntax)" in { assertDecodingSuccess( yaml = @@ -67,7 +67,7 @@ class AuthKeySha512RuleSettingsTests extends BaseRuleSettingsDecoderTest[AuthKey ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no SHA512 auth key is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthenticationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthenticationRuleSettingsTests.scala index bb7d256288..b40eddfb8e 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthenticationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthenticationRuleSettingsTests.scala @@ -21,8 +21,8 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.{BasicAuthHttpExternalAut import tech.beshu.ror.accesscontrol.blocks.rules.auth.ExternalAuthenticationRule import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} import tech.beshu.ror.mocks.MockHttpClientsFactoryWithFixedHttpClient import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.unsafeNes @@ -31,7 +31,7 @@ class ExternalAuthenticationRuleSettingsTests extends BaseRuleSettingsDecoderTest[ExternalAuthenticationRule] with MockFactory { "An ExternalAuthenticationRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one authentication service is declared" in { assertDecodingSuccess( yaml = @@ -191,7 +191,7 @@ class ExternalAuthenticationRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "extended version of rule definition doesn't declare cache TTL" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthorizationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthorizationRuleSettingsTests.scala index fd2d4c78f1..27c6e8ef53 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthorizationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ExternalAuthorizationRuleSettingsTests.scala @@ -28,8 +28,8 @@ import tech.beshu.ror.accesscontrol.domain.GroupIdLike.{GroupId, GroupIdPattern} import tech.beshu.ror.accesscontrol.domain.* import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} import tech.beshu.ror.mocks.MockHttpClientsFactoryWithFixedHttpClient import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -42,7 +42,7 @@ class ExternalAuthorizationRuleSettingsTests extends BaseRuleSettingsDecoderTest[ExternalAuthorizationRule] with MockFactory with Inside { "An ExternalAuthorizationRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one authorization service is declared" in { assertDecodingSuccess( yaml = @@ -477,7 +477,7 @@ class ExternalAuthorizationRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "authorization rule doesn't have service name declared" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/GroupsRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/GroupsRuleSettingsTests.scala index 7e0c77bda8..829d823d39 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/GroupsRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/GroupsRuleSettingsTests.scala @@ -34,8 +34,8 @@ import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolva import tech.beshu.ror.accesscontrol.blocks.variables.runtime.{RuntimeMultiResolvableVariable, RuntimeResolvableGroupsLogic} import tech.beshu.ror.accesscontrol.domain.* import tech.beshu.ror.accesscontrol.domain.GroupIdLike.{GroupId, GroupIdPattern} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} import tech.beshu.ror.mocks.{MockRequestContext, MockUserMetadataRequestContext} import tech.beshu.ror.syntax import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest @@ -71,7 +71,7 @@ class GroupsRuleSettingsTests forAll(simpleSyntaxTestParams) { (simpleSyntaxName, creator) => s"A GroupsRule settings test for $simpleSyntaxName" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "a groups mapping is not used" when { "only one group is defined" when { "one, full username is used" in { @@ -724,7 +724,7 @@ class GroupsRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "groups section is defined, but without any group" in { assertDecodingFailure( yaml = @@ -1453,7 +1453,7 @@ class GroupsRuleSettingsTests } s"A Combined GroupsRule settings" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "a groups mapping is not used" when { "only one group is defined" when { "one, full username is used" in { diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/JwtAuthRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/JwtAuthRuleSettingsTests.scala index 8a8b573863..13b5b8263c 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/JwtAuthRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/JwtAuthRuleSettingsTests.scala @@ -26,8 +26,8 @@ import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.* import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory import tech.beshu.ror.accesscontrol.factory.HttpClientsFactory.HttpClient -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} import tech.beshu.ror.mocks.MockHttpClientsFactoryWithFixedHttpClient import tech.beshu.ror.providers.EnvVarProvider.EnvVarName import tech.beshu.ror.providers.EnvVarsProvider @@ -43,7 +43,7 @@ class JwtAuthRuleSettingsTests with MockFactory { "A JwtAuthRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "rule is defined using simplified version and minimal required set of fields in JWT definition" in { assertDecodingSuccess( yaml = @@ -593,7 +593,7 @@ class JwtAuthRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no JWT definition name is defined in rule setting" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthRuleSettingsTests.scala index 61309d1260..cebc285b3c 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthRuleSettingsTests.scala @@ -18,17 +18,16 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.auth import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.LdapAuthRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.SingletonLdapContainers -import tech.beshu.ror.utils.TestsUtils.unsafeNes class LdapAuthRuleSettingsTests extends BaseRuleSettingsDecoderTest[LdapAuthRule] { "An LdapAuthRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "there is LDAP service with given name and groups are defined" in { assertDecodingSuccess( yaml = @@ -88,7 +87,7 @@ class LdapAuthRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no LDAP service with given name is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthenticationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthenticationRuleSettingsTests.scala index 53de9f6cab..2d5bb35640 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthenticationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthenticationRuleSettingsTests.scala @@ -18,17 +18,16 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.auth import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.LdapAuthenticationRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.SingletonLdapContainers -import tech.beshu.ror.utils.TestsUtils.unsafeNes class LdapAuthenticationRuleSettingsTests extends BaseRuleSettingsDecoderTest[LdapAuthenticationRule] { "An LdapAuthenticationRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "simple version of LDAP authentication rule is used" in { assertDecodingSuccess( yaml = @@ -105,7 +104,7 @@ class LdapAuthenticationRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no LDAP service with given name is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthorizationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthorizationRuleSettingsTests.scala index c34e92e55b..71c221587a 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthorizationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/LdapAuthorizationRuleSettingsTests.scala @@ -20,8 +20,8 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.LdapAuthorizationRule import tech.beshu.ror.accesscontrol.domain.GroupIdLike.{GroupId, GroupIdPattern} import tech.beshu.ror.accesscontrol.domain.{GroupIdLike, GroupsLogic, GroupIds} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.SingletonLdapContainers import tech.beshu.ror.utils.TestsUtils.unsafeNes @@ -31,7 +31,7 @@ class LdapAuthorizationRuleSettingsTests extends BaseRuleSettingsDecoderTest[LdapAuthorizationRule] { "An LdapAuthorizationRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "there is LDAP service with given name and groups are defined" in { assertDecodingSuccess( yaml = @@ -580,7 +580,7 @@ class LdapAuthorizationRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no LDAP service with given name is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ProxyAuthRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ProxyAuthRuleSettingsTests.scala index 1c21561694..da8d188c4d 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ProxyAuthRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/ProxyAuthRuleSettingsTests.scala @@ -18,8 +18,8 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.auth import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.ProxyAuthRule import tech.beshu.ror.accesscontrol.domain.User -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, RulesLevelCreationError} import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList @@ -28,7 +28,7 @@ class ProxyAuthRuleSettingsTests extends BaseRuleSettingsDecoderTest[ProxyAuthRule] { "A ProxyAuthRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one user is defined" in { assertDecodingSuccess( yaml = @@ -135,7 +135,7 @@ class ProxyAuthRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no proxy_auth data is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthRuleSettingsTests.scala index deaaceda7a..3e759ed1cd 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthRuleSettingsTests.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.RorKbnDef.SignatureCheckM import tech.beshu.ror.accesscontrol.blocks.rules.auth.RorKbnAuthRule import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.{GroupIdLike, GroupIds, GroupsLogic} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} import tech.beshu.ror.providers.EnvVarProvider.EnvVarName import tech.beshu.ror.providers.EnvVarsProvider import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest @@ -37,7 +37,7 @@ class RorKbnAuthRuleSettingsTests extends BaseRuleSettingsDecoderTest[RorKbnAuthRule] { "A RorKbnAuthRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "rule is defined using extended version with groups or logic and minimal request set of fields in ROR kbn definition" in { val rolesKeys = List("roles", "groups", "groups_or") rolesKeys.foreach { roleKey => @@ -108,7 +108,7 @@ class RorKbnAuthRuleSettingsTests } } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no ROR kbn definition name is defined in rule setting" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthenticationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthenticationRuleSettingsTests.scala index 2ffc8a5a44..228d7cd60a 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthenticationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthenticationRuleSettingsTests.scala @@ -20,8 +20,9 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.definitions.RorKbnDef import tech.beshu.ror.accesscontrol.blocks.definitions.RorKbnDef.SignatureCheckMethod import tech.beshu.ror.accesscontrol.blocks.rules.auth.RorKbnAuthenticationRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} + +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} import tech.beshu.ror.providers.EnvVarProvider.EnvVarName import tech.beshu.ror.providers.EnvVarsProvider import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest @@ -34,7 +35,7 @@ class RorKbnAuthenticationRuleSettingsTests extends BaseRuleSettingsDecoderTest[RorKbnAuthenticationRule] { "A RorKbnAuthenticationRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "rule is defined using simplified version and minimal required set of fields in ROR kbn definition" in { assertDecodingSuccess( yaml = @@ -184,7 +185,7 @@ class RorKbnAuthenticationRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "rule is defined with groups" in { val rolesKeys = List("roles_and", "groups_and") rolesKeys.foreach { roleKey => diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthorizationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthorizationRuleSettingsTests.scala index 52bb7fd7a3..3150e040b5 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthorizationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/RorKbnAuthorizationRuleSettingsTests.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.RorKbnDef.SignatureCheckM import tech.beshu.ror.accesscontrol.blocks.rules.auth.RorKbnAuthorizationRule import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.{GroupIdLike, GroupIds, GroupsLogic} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{DefinitionsLevelCreationError, GeneralReadonlyrestSettingsError, RulesLevelCreationError} import tech.beshu.ror.providers.EnvVarProvider.EnvVarName import tech.beshu.ror.providers.EnvVarsProvider import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest @@ -37,7 +37,7 @@ class RorKbnAuthorizationRuleSettingsTests extends BaseRuleSettingsDecoderTest[RorKbnAuthorizationRule] { "A RorKbnAuthorizationRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "rule is defined using extended version with groups or logic and minimal request set of fields in ROR kbn definition" in { val rolesKeys = List("roles", "groups", "groups_or") rolesKeys.foreach { roleKey => @@ -106,7 +106,7 @@ class RorKbnAuthorizationRuleSettingsTests } } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no ROR kbn definition name is defined in rule setting" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/TokenAuthenticationRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/TokenAuthenticationRuleSettingsTests.scala index e8486dc8dc..889d7a1437 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/TokenAuthenticationRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/TokenAuthenticationRuleSettingsTests.scala @@ -20,8 +20,8 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.TokenAuthenticationRule import tech.beshu.ror.accesscontrol.blocks.rules.auth.TokenAuthenticationRule.Settings import tech.beshu.ror.accesscontrol.domain.User -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.providers.EnvVarProvider.EnvVarName import tech.beshu.ror.providers.EnvVarsProvider import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest @@ -31,7 +31,7 @@ class TokenAuthenticationRuleSettingsTests extends BaseRuleSettingsDecoderTest[TokenAuthenticationRule] { "A TokenAuthenticationRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "token and username defined" in { assertDecodingSuccess( yaml = @@ -119,7 +119,7 @@ class TokenAuthenticationRuleSettingsTests ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "username is not defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/UsersRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/UsersRuleSettingsTests.scala index 957d5a70bd..f130fb09a9 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/UsersRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/auth/UsersRuleSettingsTests.scala @@ -22,11 +22,11 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.auth.UsersRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.* -import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, IndexName, RorConfigurationIndex, User} +import tech.beshu.ror.accesscontrol.domain.{CaseSensitivity, IndexName, RorSettingsIndex, User} import tech.beshu.ror.accesscontrol.factory.GlobalSettings import tech.beshu.ror.accesscontrol.factory.GlobalSettings.FlsEngine -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -38,14 +38,14 @@ class UsersRuleSettingsTests extends BaseRuleSettingsDecoderTest[UsersRule] { showBasicAuthPrompt = true, forbiddenRequestMessage = "Forbidden", flsEngine = FlsEngine.default, - configurationIndex = RorConfigurationIndex(IndexName.Full(".readonlyrest")), + settingsIndex = RorSettingsIndex(IndexName.Full(".readonlyrest")), userIdCaseSensitivity = CaseSensitivity.Enabled, usersDefinitionDuplicateUsernamesValidationEnabled = true ), ) "A UsersRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one user is defined" in { assertDecodingSuccess( yaml = @@ -104,7 +104,7 @@ class UsersRuleSettingsTests extends BaseRuleSettingsDecoderTest[UsersRule] { ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no user is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/ActionRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/ActionRuleSettingsTests.scala index f45699553e..a28c0433a1 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/ActionRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/ActionRuleSettingsTests.scala @@ -20,15 +20,15 @@ import cats.data.NonEmptySet import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.elasticsearch.ActionsRule import tech.beshu.ror.accesscontrol.domain.Action -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest class ActionRuleSettingsTests extends BaseRuleSettingsDecoderTest[ActionsRule] { "An ActionRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one action is defined" in { assertDecodingSuccess( yaml = @@ -64,7 +64,7 @@ class ActionRuleSettingsTests extends BaseRuleSettingsDecoderTest[ActionsRule] { ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no action is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/DataStreamsRuleSettingsTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/DataStreamsRuleSettingsTest.scala index ec351fd9d0..6d1b411ecb 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/DataStreamsRuleSettingsTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/DataStreamsRuleSettingsTest.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.elasticsearch.DataStreamsRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.DataStreamName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -31,7 +31,7 @@ import tech.beshu.ror.utils.TestsUtils.* class DataStreamsRuleSettingsTest extends BaseRuleSettingsDecoderTest[DataStreamsRule] { "A DataStreamsRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one data stream is defined" in { assertDecodingSuccess( yaml = @@ -113,7 +113,7 @@ class DataStreamsRuleSettingsTest extends BaseRuleSettingsDecoderTest[DataStream ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no data stream is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/FieldsRuleSettingsTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/FieldsRuleSettingsTest.scala index ca10151ed1..4e66aadc77 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/FieldsRuleSettingsTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/FieldsRuleSettingsTest.scala @@ -21,15 +21,15 @@ import tech.beshu.ror.accesscontrol.blocks.rules.elasticsearch.FieldsRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.FieldLevelSecurity.FieldsRestrictions.{AccessMode, DocumentField} import tech.beshu.ror.accesscontrol.factory.GlobalSettings.FlsEngine -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{GeneralReadonlyrestSettingsError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{GeneralReadonlyrestSettingsError, RulesLevelCreationError} import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* class FieldsRuleSettingsTest extends BaseRuleSettingsDecoderTest[FieldsRule] { "A FieldsRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "ror is run in plugin mode and" when { "only one field is defined" in { assertDecodingSuccess( @@ -171,7 +171,7 @@ class FieldsRuleSettingsTest extends BaseRuleSettingsDecoderTest[FieldsRule] { } } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no field is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/FilterRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/FilterRuleSettingsTests.scala index c585ce99a1..9b67346d9c 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/FilterRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/FilterRuleSettingsTests.scala @@ -20,15 +20,15 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.elasticsearch.FilterRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeSingleResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.Filter -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.unsafeNes class FilterRuleSettingsTests extends BaseRuleSettingsDecoderTest[FilterRule] { "A FilterRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "filter is defined" in { assertDecodingSuccess( yaml = @@ -65,7 +65,7 @@ class FilterRuleSettingsTests extends BaseRuleSettingsDecoderTest[FilterRule] { ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no filter is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/IndicesRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/IndicesRuleSettingsTests.scala index 27493408a2..af95c76abd 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/IndicesRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/IndicesRuleSettingsTests.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.elasticsearch.indices.IndicesRu import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.ClusterIndexName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -31,7 +31,7 @@ import tech.beshu.ror.utils.TestsUtils.* class IndicesRuleSettingsTests extends BaseRuleSettingsDecoderTest[IndicesRule] { "An IndicesRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one index is defined" in { assertDecodingSuccess( yaml = @@ -240,7 +240,7 @@ class IndicesRuleSettingsTests extends BaseRuleSettingsDecoderTest[IndicesRule] ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no index is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/RepositoriesRuleSettingsTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/RepositoriesRuleSettingsTest.scala index 4e3c64a533..ce959fb81f 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/RepositoriesRuleSettingsTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/RepositoriesRuleSettingsTest.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.elasticsearch.RepositoriesRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.RepositoryName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -31,7 +31,7 @@ import tech.beshu.ror.utils.TestsUtils.* class RepositoriesRuleSettingsTest extends BaseRuleSettingsDecoderTest[RepositoriesRule] { "A RepositoriesRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one repository is defined" in { assertDecodingSuccess( yaml = @@ -114,7 +114,7 @@ class RepositoriesRuleSettingsTest extends BaseRuleSettingsDecoderTest[Repositor ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no repository is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/ResponseFieldsRuleSettingsTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/ResponseFieldsRuleSettingsTest.scala index 6e7b4b777f..af7f823f4d 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/ResponseFieldsRuleSettingsTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/ResponseFieldsRuleSettingsTest.scala @@ -20,15 +20,15 @@ import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.elasticsearch.ResponseFieldsRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.ResponseFieldsFiltering.* -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* class ResponseFieldsRuleSettingsTest extends BaseRuleSettingsDecoderTest[ResponseFieldsRule] { "A ResponseFieldsRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "ror is run in plugin mode and" when { "only one field is defined" in { assertDecodingSuccess( @@ -142,7 +142,7 @@ class ResponseFieldsRuleSettingsTest extends BaseRuleSettingsDecoderTest[Respons } } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no field is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/SnapshotsRuleSettingsTest.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/SnapshotsRuleSettingsTest.scala index 4d3458ccce..2a3698ad60 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/SnapshotsRuleSettingsTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/elasticsearch/SnapshotsRuleSettingsTest.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.elasticsearch.SnapshotsRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.SnapshotName -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -31,7 +31,7 @@ import tech.beshu.ror.utils.TestsUtils.* class SnapshotsRuleSettingsTest extends BaseRuleSettingsDecoderTest[SnapshotsRule] { "A SnapshotsRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one snapshot is defined" in { assertDecodingSuccess( yaml = @@ -113,7 +113,7 @@ class SnapshotsRuleSettingsTest extends BaseRuleSettingsDecoderTest[SnapshotsRul ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no snapshot is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/HeadersAndRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/HeadersAndRuleSettingsTests.scala index 803f4dc288..2f1285b953 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/HeadersAndRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/HeadersAndRuleSettingsTests.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.http import cats.data.NonEmptySet import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.http.HeadersAndRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.syntax.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest @@ -29,7 +29,7 @@ import tech.beshu.ror.utils.TestsUtils.* class HeadersAndRuleSettingsTests extends BaseRuleSettingsDecoderTest[HeadersAndRule] { "A HeadersAndRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one header requirement is defined" in { assertDecodingSuccess( yaml = @@ -111,7 +111,7 @@ class HeadersAndRuleSettingsTests extends BaseRuleSettingsDecoderTest[HeadersAnd ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no header is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/HeadersOrRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/HeadersOrRuleSettingsTests.scala index 6159d044db..5421d44736 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/HeadersOrRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/HeadersOrRuleSettingsTests.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.http import cats.data.NonEmptySet import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.http.HeadersOrRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.syntax.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest @@ -29,7 +29,7 @@ import tech.beshu.ror.utils.TestsUtils.* class HeadersOrRuleSettingsTests extends BaseRuleSettingsDecoderTest[HeadersOrRule] { "A HeadersOrRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one header requirement is defined" in { assertDecodingSuccess( yaml = @@ -92,7 +92,7 @@ class HeadersOrRuleSettingsTests extends BaseRuleSettingsDecoderTest[HeadersOrRu ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no header is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/MaxBodyLengthRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/MaxBodyLengthRuleSettingsTests.scala index 1c25319e30..36b4bb069d 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/MaxBodyLengthRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/MaxBodyLengthRuleSettingsTests.scala @@ -19,15 +19,15 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.http import org.scalatest.matchers.should.Matchers.* import squants.information.Bytes import tech.beshu.ror.accesscontrol.blocks.rules.http.MaxBodyLengthRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.syntax.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest class MaxBodyLengthRuleSettingsTests extends BaseRuleSettingsDecoderTest[MaxBodyLengthRule] { "A MaxBodyLengthRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "max body length > 0 is defined" in { assertDecodingSuccess( yaml = @@ -63,7 +63,7 @@ class MaxBodyLengthRuleSettingsTests extends BaseRuleSettingsDecoderTest[MaxBody ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "max body length field is defined, but no value it set" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/MethodsRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/MethodsRuleSettingsTests.scala index bc96967102..349c347738 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/MethodsRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/MethodsRuleSettingsTests.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.http import cats.data.NonEmptySet import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.http.MethodsRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.accesscontrol.request.RequestContext.Method import tech.beshu.ror.syntax.* @@ -29,7 +29,7 @@ import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTes class MethodsRuleSettingsTests extends BaseRuleSettingsDecoderTest[MethodsRule] { "A MethodsRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "one http method is defined" in { assertDecodingSuccess( yaml = @@ -66,7 +66,7 @@ class MethodsRuleSettingsTests extends BaseRuleSettingsDecoderTest[MethodsRule] ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no http method is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/SessionMaxIdleRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/SessionMaxIdleRuleSettingsTests.scala index 421fa0dcb5..ad448384cc 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/SessionMaxIdleRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/SessionMaxIdleRuleSettingsTests.scala @@ -18,8 +18,8 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.http import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.http.SessionMaxIdleRule -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.syntax.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.DurationOps.* @@ -30,7 +30,7 @@ import scala.language.postfixOps class SessionMaxIdleRuleSettingsTests extends BaseRuleSettingsDecoderTest[SessionMaxIdleRule] { "A SessionMaxIdleRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "max idle time of session is > 0s" in { assertDecodingSuccess( yaml = @@ -66,7 +66,7 @@ class SessionMaxIdleRuleSettingsTests extends BaseRuleSettingsDecoderTest[Sessio ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "session max idle time is not defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/UriRegexRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/UriRegexRuleSettingsTests.scala index 5f8bb1b2c6..e26570eb25 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/UriRegexRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/UriRegexRuleSettingsTests.scala @@ -24,8 +24,8 @@ import tech.beshu.ror.accesscontrol.blocks.BlockContext.CurrentUserMetadataReque import tech.beshu.ror.accesscontrol.blocks.metadata.UserMetadata import tech.beshu.ror.accesscontrol.blocks.rules.http.UriRegexRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.ToBeResolved -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.request.RequestContext import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.syntax.* @@ -33,7 +33,7 @@ import tech.beshu.ror.syntax.* class UriRegexRuleSettingsTests extends BaseRuleSettingsDecoderTest[UriRegexRule] with MockFactory { "A UriRegexRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "single uri pattern is defined" in { assertDecodingSuccess( yaml = @@ -117,7 +117,7 @@ class UriRegexRuleSettingsTests extends BaseRuleSettingsDecoderTest[UriRegexRule ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no uri pattern is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/XForwardedForRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/XForwardedForRuleSettingsTests.scala index 1687789251..ae76dbe34e 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/XForwardedForRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/http/XForwardedForRuleSettingsTests.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.http.XForwardedForRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.Address -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.syntax.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest @@ -32,7 +32,7 @@ import tech.beshu.ror.utils.TestsUtils.* class XForwardedForRuleSettingsTests extends BaseRuleSettingsDecoderTest[XForwardedForRule] { "A XForwardedForRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one address is defined" in { assertDecodingSuccess( yaml = @@ -148,7 +148,7 @@ class XForwardedForRuleSettingsTests extends BaseRuleSettingsDecoderTest[XForwar ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no address or ip is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaAccessRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaAccessRuleSettingsTests.scala index 36c128e524..791c011301 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaAccessRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaAccessRuleSettingsTests.scala @@ -18,16 +18,16 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.kibana import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.kibana.KibanaAccessRule -import tech.beshu.ror.accesscontrol.domain.{IndexName, KibanaAccess, RorConfigurationIndex} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.domain.{IndexName, KibanaAccess, RorSettingsIndex} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.unsafeNes class KibanaAccessRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaAccessRule] { "A KibanaAccess" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "ro access is defined" in { assertDecodingSuccess( yaml = @@ -42,7 +42,7 @@ class KibanaAccessRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaAc |""".stripMargin, assertion = rule => { rule.settings.access should be(KibanaAccess.RO) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -60,7 +60,7 @@ class KibanaAccessRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaAc |""".stripMargin, assertion = rule => { rule.settings.access should be(KibanaAccess.RW) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -78,7 +78,7 @@ class KibanaAccessRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaAc |""".stripMargin, assertion = rule => { rule.settings.access should be(KibanaAccess.ROStrict) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -96,7 +96,7 @@ class KibanaAccessRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaAc |""".stripMargin, assertion = rule => { rule.settings.access should be(KibanaAccess.Admin) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -114,7 +114,7 @@ class KibanaAccessRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaAc |""".stripMargin, assertion = rule => { rule.settings.access should be(KibanaAccess.ApiOnly) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -132,7 +132,7 @@ class KibanaAccessRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaAc |""".stripMargin, assertion = rule => { rule.settings.access should be(KibanaAccess.Unrestricted) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -151,12 +151,12 @@ class KibanaAccessRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaAc |""".stripMargin, assertion = rule => { rule.settings.access should be(KibanaAccess.Unrestricted) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no access is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaHideAppsRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaHideAppsRuleSettingsTests.scala index 32995b6a61..a9666b8bb4 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaHideAppsRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaHideAppsRuleSettingsTests.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.kibana import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.kibana.KibanaHideAppsRule import tech.beshu.ror.accesscontrol.domain.KibanaApp.FullNameKibanaApp -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.{kibanaAppRegex, unsafeNes} import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList @@ -28,7 +28,7 @@ import tech.beshu.ror.utils.uniquelist.UniqueNonEmptyList class KibanaHideAppsRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaHideAppsRule] { "A KibanaHideAppsRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one kibana app is defined" in { assertDecodingSuccess( yaml = @@ -90,7 +90,7 @@ class KibanaHideAppsRuleSettingsTests extends BaseRuleSettingsDecoderTest[Kibana ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "empty string kibana app is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaIndexRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaIndexRuleSettingsTests.scala index 919c8bc6dd..9dc5f0af31 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaIndexRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaIndexRuleSettingsTests.scala @@ -19,15 +19,15 @@ package tech.beshu.ror.unit.acl.factory.decoders.rules.kibana import org.scalatest.matchers.should.Matchers.* import tech.beshu.ror.accesscontrol.blocks.rules.kibana.KibanaIndexRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeSingleResolvableVariable.{AlreadyResolved, ToBeResolved} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* class KibanaIndexRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaIndexRule] { "A KibanaIndexRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "kibana index is defined" in { assertDecodingSuccess( yaml = @@ -83,7 +83,7 @@ class KibanaIndexRuleSettingsTests extends BaseRuleSettingsDecoderTest[KibanaInd ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no kibana index is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaUserDataRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaUserDataRuleSettingsTests.scala index 095a3d8e49..78088db83d 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaUserDataRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/kibana/KibanaUserDataRuleSettingsTests.scala @@ -29,8 +29,8 @@ import tech.beshu.ror.accesscontrol.domain.Json.{JsonRepresentation, JsonTree} import tech.beshu.ror.accesscontrol.domain.KibanaAllowedApiPath.AllowedHttpMethod import tech.beshu.ror.accesscontrol.domain.KibanaAllowedApiPath.AllowedHttpMethod.HttpMethod import tech.beshu.ror.accesscontrol.domain.KibanaApp.FullNameKibanaApp -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.{MalformedValue, Message} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.{BlocksLevelCreationError, RulesLevelCreationError} import tech.beshu.ror.syntax.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -40,7 +40,7 @@ class KibanaUserDataRuleSettingsTests with OptionValues with EitherValues { "A KibanaUserDataRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "all required properties are set" in { assertDecodingSuccess( yaml = @@ -60,7 +60,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -89,7 +89,7 @@ class KibanaUserDataRuleSettingsTests Set(KibanaAllowedApiPath(AllowedHttpMethod.Any, JavaRegex.compile("""^/api/spaces/.*$""").get)) ) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -113,7 +113,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -136,7 +136,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -159,7 +159,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -182,7 +182,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -204,7 +204,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.kibanaTemplateIndex should be(None) rule.settings.appsToHide should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -228,7 +228,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -252,7 +252,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -279,7 +279,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set(FullNameKibanaApp("app1"))) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -306,7 +306,7 @@ class KibanaUserDataRuleSettingsTests )) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -330,7 +330,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -358,7 +358,7 @@ class KibanaUserDataRuleSettingsTests Set(KibanaAllowedApiPath(AllowedHttpMethod.Any, JavaRegex.compile("""^/api/spaces/.*$""").get)) ) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -387,7 +387,7 @@ class KibanaUserDataRuleSettingsTests )) ) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -418,7 +418,7 @@ class KibanaUserDataRuleSettingsTests )) ) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -454,7 +454,7 @@ class KibanaUserDataRuleSettingsTests ) )) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -478,7 +478,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -505,7 +505,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -531,7 +531,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(None) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -579,7 +579,7 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(Some(resolvableMetadataJsonRepresentation)) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } @@ -603,13 +603,13 @@ class KibanaUserDataRuleSettingsTests rule.settings.appsToHide should be(Set.empty) rule.settings.allowedApiPaths should be(Set.empty) rule.settings.metadata should be(Some(JsonTree.Value(AlreadyResolved(NullValue)))) - rule.settings.rorIndex should be(RorConfigurationIndex(IndexName.Full(".readonlyrest"))) + rule.settings.rorIndex should be(RorSettingsIndex(IndexName.Full(".readonlyrest"))) } ) } } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "some of the required properties are not set" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/transport/HostsRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/transport/HostsRuleSettingsTests.scala index 779c3fc6ec..82e478b9af 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/transport/HostsRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/transport/HostsRuleSettingsTests.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.tranport.HostsRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.Address -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -31,7 +31,7 @@ import tech.beshu.ror.utils.TestsUtils.* class HostsRuleSettingsTests extends BaseRuleSettingsDecoderTest[HostsRule] { "A HostsRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one host is defined" in { assertDecodingSuccess( yaml = @@ -113,7 +113,7 @@ class HostsRuleSettingsTests extends BaseRuleSettingsDecoderTest[HostsRule] { ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no host is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/transport/LocalHostsRuleSettingsTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/transport/LocalHostsRuleSettingsTests.scala index 4cdfe0db3b..1a7c8d5333 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/transport/LocalHostsRuleSettingsTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/factory/decoders/rules/transport/LocalHostsRuleSettingsTests.scala @@ -22,8 +22,8 @@ import tech.beshu.ror.accesscontrol.blocks.rules.tranport.LocalHostsRule import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable import tech.beshu.ror.accesscontrol.blocks.variables.runtime.RuntimeMultiResolvableVariable.{AlreadyResolved, ToBeResolved} import tech.beshu.ror.accesscontrol.domain.Address -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.MalformedValue -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.RulesLevelCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.MalformedValue +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.RulesLevelCreationError import tech.beshu.ror.accesscontrol.orders.* import tech.beshu.ror.unit.acl.factory.decoders.rules.BaseRuleSettingsDecoderTest import tech.beshu.ror.utils.TestsUtils.* @@ -31,7 +31,7 @@ import tech.beshu.ror.utils.TestsUtils.* class LocalHostsRuleSettingsTests extends BaseRuleSettingsDecoderTest[LocalHostsRule] { "A LocalHostsRule" should { - "be able to be loaded from config" when { + "be able to be loaded from settings" when { "only one host is defined" in { assertDecodingSuccess( yaml = @@ -90,7 +90,7 @@ class LocalHostsRuleSettingsTests extends BaseRuleSettingsDecoderTest[LocalHosts ) } } - "not be able to be loaded from config" when { + "not be able to be loaded from settings" when { "no host is defined" in { assertDecodingFailure( yaml = diff --git a/core/src/test/scala/tech/beshu/ror/unit/acl/logging/AuditingToolTests.scala b/core/src/test/scala/tech/beshu/ror/unit/acl/logging/AuditingToolTests.scala index 46e655e555..fafb87da9f 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/acl/logging/AuditingToolTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/acl/logging/AuditingToolTests.scala @@ -45,7 +45,7 @@ import tech.beshu.ror.audit.{AuditLogSerializer, AuditResponseContext} import tech.beshu.ror.es.{DataStreamBasedAuditSinkService, DataStreamService, IndexBasedAuditSinkService} import tech.beshu.ror.mocks.MockRequestContext import tech.beshu.ror.syntax.* -import tech.beshu.ror.utils.TestsUtils.{fullDataStreamName, fullIndexName, nes, testAuditEnvironmentContext, unsafeNes} +import tech.beshu.ror.utils.TestsUtils.* import java.time.* import java.util.UUID @@ -72,7 +72,7 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter override def index(cluster: AuditCluster): IndexBasedAuditSinkService = mock[IndexBasedAuditSinkService] } ).runSyncUnsafe().toOption.flatten.get - auditingTool.audit(createAllowedResponseContext(Policy.Allow, Verbosity.Error), testAuditEnvironmentContext).runSyncUnsafe() + auditingTool.audit(createAllowedResponseContext(Policy.Allow, Verbosity.Error)).runSyncUnsafe() } "custom serializer throws exception" in { val auditingTool = AuditingTool.create( @@ -85,7 +85,7 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter } ).runSyncUnsafe().toOption.flatten.get an[IllegalArgumentException] should be thrownBy { - auditingTool.audit(createAllowedResponseContext(Policy.Allow, Verbosity.Info), testAuditEnvironmentContext).runSyncUnsafe() + auditingTool.audit(createAllowedResponseContext(Policy.Allow, Verbosity.Info)).runSyncUnsafe() } } } @@ -107,7 +107,7 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter override def index(cluster: AuditCluster): IndexBasedAuditSinkService = indexAuditSink } ).runSyncUnsafe().toOption.flatten.get - auditingTool.audit(createAllowedResponseContext(Policy.Allow, Verbosity.Info), testAuditEnvironmentContext).runSyncUnsafe() + auditingTool.audit(createAllowedResponseContext(Policy.Allow, Verbosity.Info)).runSyncUnsafe() } "request was matched by forbidden rule" in { val requestId = RequestId("mock-1") @@ -141,7 +141,7 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter Vector.empty ) - auditingTool.audit(responseContext, testAuditEnvironmentContext).runSyncUnsafe() + auditingTool.audit(responseContext).runSyncUnsafe() } "request was forbidden" in { val requestId = RequestId("mock-1") @@ -164,7 +164,7 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter val requestContext = MockRequestContext.indices.copy(timestamp = someday.toInstant, id = RequestContext.Id.fromString("mock-1")) val responseContext = Forbidden(requestContext, Vector.empty) - auditingTool.audit(responseContext, testAuditEnvironmentContext).runSyncUnsafe() + auditingTool.audit(responseContext).runSyncUnsafe() } "request was finished with error" in { val requestId = RequestId("mock-1") @@ -187,12 +187,12 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter val requestContext = MockRequestContext.indices.copy(timestamp = someday.toInstant, id = RequestContext.Id.fromString("mock-1")) val responseContext = Errored(requestContext, new Exception("error")) - auditingTool.audit(responseContext, testAuditEnvironmentContext).runSyncUnsafe() + auditingTool.audit(responseContext).runSyncUnsafe() } } } "log sink is used" should { - "saved audit log to file defined in log4j config" in { + "saved audit log to file defined in log4j settings" in { @nowarn("cat=deprecation") val auditingTool = AuditingTool.create( settings = AuditSettings( @@ -201,7 +201,8 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter new DefaultAuditLogSerializer, RorAuditLoggerName.default )) - ) + ), + testEsNodeSettings ), auditSinkServiceCreator = new DataStreamAndIndexBasedAuditSinkServiceCreator { override def dataStream(cluster: AuditCluster): DataStreamBasedAuditSinkService = mock[DataStreamBasedAuditSinkService] @@ -216,7 +217,7 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter auditLogFile.overwrite("") - auditingTool.audit(responseContext, testAuditEnvironmentContext).runSyncUnsafe() + auditingTool.audit(responseContext).runSyncUnsafe() val logFileContent = auditLogFile.contentAsString logFileContent should include(requestContextId.value) @@ -226,7 +227,7 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter "no enabled outputs in settings" should { "be disabled" in { val creationResult = AuditingTool.create( - settings = AuditSettings(NonEmptyList.of(AuditSink.Disabled, AuditSink.Disabled, AuditSink.Disabled)), + settings = AuditSettings(NonEmptyList.of(AuditSink.Disabled, AuditSink.Disabled, AuditSink.Disabled), testEsNodeSettings), auditSinkServiceCreator = new DataStreamAndIndexBasedAuditSinkServiceCreator { override def dataStream(cluster: AuditCluster): DataStreamBasedAuditSinkService = mock[DataStreamBasedAuditSinkService] @@ -238,18 +239,21 @@ class AuditingToolTests extends AnyWordSpec with MockFactory with BeforeAndAfter } } - private def auditSettings(serializer: AuditLogSerializer) = AuditSettings(NonEmptyList.of( - AuditSink.Enabled(Config.EsIndexBasedSink( - serializer, - RorAuditIndexTemplate.from("'test_'yyyy-MM-dd").toOption.get, - AuditCluster.LocalAuditCluster - )), - AuditSink.Enabled(Config.EsDataStreamBasedSink( - serializer, - RorAuditDataStream.from("test_ds").toOption.get, - AuditCluster.LocalAuditCluster - )) - )) + private def auditSettings(serializer: AuditLogSerializer) = AuditSettings( + auditSinks = NonEmptyList.of( + AuditSink.Enabled(Config.EsIndexBasedSink( + serializer, + RorAuditIndexTemplate.from("'test_'yyyy-MM-dd").toOption.get, + AuditCluster.LocalAuditCluster + )), + AuditSink.Enabled(Config.EsDataStreamBasedSink( + serializer, + RorAuditDataStream.from("test_ds").toOption.get, + AuditCluster.LocalAuditCluster + )) + ), + esNodeSettings = testEsNodeSettings + ) private lazy val someday = ZonedDateTime.of(2019, 1, 1, 0, 1, 59, 0, ZoneId.of("+1")) diff --git a/core/src/test/scala/tech/beshu/ror/unit/boot/IndexSettingsRelatedRorCoreTest.scala b/core/src/test/scala/tech/beshu/ror/unit/boot/IndexSettingsRelatedRorCoreTest.scala new file mode 100644 index 0000000000..ee07831408 --- /dev/null +++ b/core/src/test/scala/tech/beshu/ror/unit/boot/IndexSettingsRelatedRorCoreTest.scala @@ -0,0 +1,331 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.unit.boot + +import better.files.File +import cats.implicits.toShow +import eu.timepit.refined.types.string.NonEmptyString +import io.circe.Json +import monix.eval.Task +import monix.execution.Scheduler.Implicits.global +import org.apache.commons.text.StringEscapeUtils.escapeJava +import org.scalamock.scalatest.MockFactory +import org.scalatest.concurrent.Eventually +import org.scalatest.matchers.should.Matchers.* +import org.scalatest.wordspec.AnyWordSpec +import org.scalatest.{EitherValues, Inside, OptionValues} +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.AccessControlList +import tech.beshu.ror.accesscontrol.AccessControlList.AccessControlStaticContext +import tech.beshu.ror.accesscontrol.audit.sink.AuditSinkServiceCreator +import tech.beshu.ror.accesscontrol.domain.{IndexName, RequestId, RorSettingsFile} +import tech.beshu.ror.accesscontrol.factory.{Core, CoreFactory, RorDependencies} +import tech.beshu.ror.boot.ReadonlyRest +import tech.beshu.ror.boot.RorInstance.TestSettings +import tech.beshu.ror.es.{EsEnv, IndexDocumentManager} +import tech.beshu.ror.implicits.* +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings +import tech.beshu.ror.settings.ror.RawRorSettings +import tech.beshu.ror.syntax.* +import tech.beshu.ror.unit.utils.WithReadonlyrestBootSupport +import tech.beshu.ror.utils.DurationOps.* +import tech.beshu.ror.utils.TestsUtils.* + +import java.util.UUID +import scala.concurrent.duration.* +import scala.language.postfixOps + +class IndexSettingsRelatedRorCoreTest extends AnyWordSpec + with WithReadonlyrestBootSupport + with Inside with OptionValues with EitherValues + with MockFactory with Eventually { + + private val defaultRorIndexName: NonEmptyString = ".readonlyrest" + private val customRorIndexName: NonEmptyString = "custom_ror_index" + + private val mainInIndexRorSettingsDocumentId = "1" + private val testInIndexRorSettingsDocumentId = "2" + + "A ReadonlyREST core" should { + "load ROR index name" when { + "no index is defined in settings" should { + "start ROR with default index name" in withReadonlyRest { + val indexDocumentManager = mock[IndexDocumentManager] + mockInIndexMainSettingsLoading(indexDocumentManager, defaultRorIndexName) + mockInIndexTestSettingsLoading(indexDocumentManager, defaultRorIndexName) + + val coreFactory = mock[CoreFactory] + mockCoreFactory(coreFactory, indexRorSettings) + + ( + createReadonlyRestBoot(coreFactory, indexDocumentManager), + createEsConfigBasedRorSettings("/boot_tests/index_settings//no_index_defined/") + ) + } { _ => + // nothing - just should start + } + "save ROR settings in default index" in withReadonlyRestExt { + val indexDocumentManager = mock[IndexDocumentManager] + mockInIndexMainSettingsLoading(indexDocumentManager, defaultRorIndexName) + mockInIndexTestSettingsLoading(indexDocumentManager, defaultRorIndexName) + + val coreFactory = mock[CoreFactory] + mockCoreFactory(coreFactory, indexRorSettings) + + ( + createReadonlyRestBoot(coreFactory, indexDocumentManager), + createEsConfigBasedRorSettings("/boot_tests/index_settings//no_index_defined/"), + (indexDocumentManager, coreFactory) + ) + } { case (rorInstance, (indexDocumentManager, coreFactory)) => + + mockCoreFactory(coreFactory, updatedRorSettings) + mockInIndexMainSettingsSaving(indexDocumentManager, defaultRorIndexName, updatedRorSettings) + + val forceReloadingResult = + rorInstance + .forceReloadAndSave(updatedRorSettings)(newRequestId()) + .runSyncUnsafe() + + forceReloadingResult should be(Right(())) + } + "save ROR test settings in default index" in withReadonlyRestExt { + val indexDocumentManager = mock[IndexDocumentManager] + mockInIndexMainSettingsLoading(indexDocumentManager, defaultRorIndexName) + mockInIndexTestSettingsLoading(indexDocumentManager, defaultRorIndexName) + + val coreFactory = mock[CoreFactory] + mockCoreFactory(coreFactory, indexRorSettings) + + ( + createReadonlyRestBoot(coreFactory, indexDocumentManager), + createEsConfigBasedRorSettings("/boot_tests/index_settings//no_index_defined/"), + (indexDocumentManager, coreFactory) + ) + } { case (rorInstance, (indexDocumentManager, coreFactory)) => + + mockCoreFactory(coreFactory, updatedRorSettings) + mockInIndexTestSettingsSaving(indexDocumentManager, defaultRorIndexName, updatedRorSettings) + + val forceReloadingResult = + rorInstance + .forceReloadTestSettingsEngine(updatedRorSettings, (5 minutes).toRefinedPositiveUnsafe)(newRequestId()) + .runSyncUnsafe() + + forceReloadingResult.value shouldBe a[TestSettings.Present] + } + } + "custom index is defined in settings" should { + "start ROR with custom index name" in withReadonlyRest { + val indexDocumentManager = mock[IndexDocumentManager] + mockInIndexMainSettingsLoading(indexDocumentManager, customRorIndexName) + mockInIndexTestSettingsLoading(indexDocumentManager, customRorIndexName) + + val coreFactory = mock[CoreFactory] + mockCoreFactory(coreFactory, indexRorSettings) + + ( + createReadonlyRestBoot(coreFactory, indexDocumentManager), + createEsConfigBasedRorSettings("/boot_tests/index_settings//custom_index_defined/") + ) + } { _ => + // nothing - just should start + } + "save ROR settings in custom index" in withReadonlyRestExt { + val indexDocumentManager = mock[IndexDocumentManager] + mockInIndexMainSettingsLoading(indexDocumentManager, customRorIndexName) + mockInIndexTestSettingsLoading(indexDocumentManager, customRorIndexName) + + val coreFactory = mock[CoreFactory] + mockCoreFactory(coreFactory, indexRorSettings) + + ( + createReadonlyRestBoot(coreFactory, indexDocumentManager), + createEsConfigBasedRorSettings("/boot_tests/index_settings//custom_index_defined/"), + (indexDocumentManager, coreFactory) + ) + } { case (rorInstance, (indexDocumentManager, coreFactory)) => + + mockCoreFactory(coreFactory, updatedRorSettings) + mockInIndexMainSettingsSaving(indexDocumentManager, customRorIndexName, updatedRorSettings) + + val forceReloadingResult = + rorInstance + .forceReloadAndSave(updatedRorSettings)(newRequestId()) + .runSyncUnsafe() + + forceReloadingResult should be(Right(())) + } + "save ROR test settings in custom index" in withReadonlyRestExt { + val indexDocumentManager = mock[IndexDocumentManager] + mockInIndexMainSettingsLoading(indexDocumentManager, customRorIndexName) + mockInIndexTestSettingsLoading(indexDocumentManager, customRorIndexName) + + val coreFactory = mock[CoreFactory] + mockCoreFactory(coreFactory, indexRorSettings) + + ( + createReadonlyRestBoot(coreFactory, indexDocumentManager), + createEsConfigBasedRorSettings("/boot_tests/index_settings//custom_index_defined/"), + (indexDocumentManager, coreFactory) + ) + } { case (rorInstance, (indexDocumentManager, coreFactory)) => + + mockCoreFactory(coreFactory, updatedRorSettings) + mockInIndexTestSettingsSaving(indexDocumentManager, customRorIndexName, updatedRorSettings) + + val forceReloadingResult = + rorInstance + .forceReloadTestSettingsEngine(updatedRorSettings, (5 minutes).toRefinedPositiveUnsafe)(newRequestId()) + .runSyncUnsafe() + + forceReloadingResult.value shouldBe a[TestSettings.Present] + } + } + } + } + + private def createReadonlyRestBoot(factory: CoreFactory, + indexDocumentManager: IndexDocumentManager) = { + implicit val systemContext: SystemContext = SystemContext.default + ReadonlyRest.create(factory, indexDocumentManager, mock[AuditSinkServiceCreator]) + } + + private def mockCoreFactory(mockedCoreFactory: CoreFactory, + rawRorSettings: RawRorSettings): CoreFactory = { + (mockedCoreFactory.createCoreFrom _) + .expects(where { + (settings: RawRorSettings, _, _, _, _) => settings == rawRorSettings + }) + .once() + .returns(Task.now(Right(Core(mockAccessControl, RorDependencies.noOp, None)))) + mockedCoreFactory + } + + private def mockInIndexMainSettingsLoading(indexDocumentManager: IndexDocumentManager, + indexName: NonEmptyString) = { + (indexDocumentManager.documentAsJson _) + .expects(fullIndexName(indexName), mainInIndexRorSettingsDocumentId) + .once() + .returns(Task.now(Right(circeJsonFrom(s"""{ "settings": "${escapeJava(indexRorSettings.rawYaml)}" }""")))) + } + + private def mockInIndexTestSettingsLoading(indexDocumentManager: IndexDocumentManager, + indexName: NonEmptyString) = { + (indexDocumentManager.documentAsJson _) + .expects(fullIndexName(indexName), testInIndexRorSettingsDocumentId) + .once() + .returns(Task.now(Left(IndexDocumentManager.DocumentNotFound))) + } + + private def mockInIndexMainSettingsSaving(indexDocumentManager: IndexDocumentManager, + indexName: NonEmptyString, + rawRorSettings: RawRorSettings) = { + (indexDocumentManager.saveDocumentJson _) + .expects(fullIndexName(indexName), mainInIndexRorSettingsDocumentId, circeJsonFrom(s"""{ "settings": "${escapeJava(rawRorSettings.rawYaml)}"}""")) + .once() + .returns(Task.now(Right(()))) + } + + private def mockInIndexTestSettingsSaving(indexDocumentManager: IndexDocumentManager, + indexName: NonEmptyString, + rawRorSettings: RawRorSettings) = { + (indexDocumentManager.saveDocumentJson _) + .expects( + where { + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(indexName) && + id == testInIndexRorSettingsDocumentId && + document.hcursor.get[String]("settings").toOption.contains(rawRorSettings.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("300000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded + } + ) + .once() + .returns(Task.now(Right(()))) + } + + private def mockAccessControl = { + val mockedAccessControl = mock[AccessControlList] + (() => mockedAccessControl.staticContext) + .expects() + .anyNumberOfTimes() + .returns(mockAccessControlStaticContext) + (() => mockedAccessControl.description) + .expects() + .anyNumberOfTimes() + .returns("ENABLED") + mockedAccessControl + } + + private def mockAccessControlStaticContext = { + val mockedContext = mock[AccessControlStaticContext] + (() => mockedContext.obfuscatedHeaders) + .expects() + .anyNumberOfTimes() + .returns(Set.empty) + + (() => mockedContext.usedFlsEngineInFieldsRule) + .expects() + .anyNumberOfTimes() + .returns(None) + mockedContext + } + + private def newRequestId() = RequestId(UUID.randomUUID().toString) + + private def createEsConfigBasedRorSettings(resourceEsConfigDir: String): EsConfigBasedRorSettings = { + implicit val systemContext: SystemContext = SystemContext.default + val esConfig = File(getResourcePath(resourceEsConfigDir)) + val esEnv = EsEnv(esConfig, esConfig, defaultEsVersionForTests, testEsNodeSettings) + EsConfigBasedRorSettings.from(esEnv).runSyncUnsafe() match { + case Right(settings) => + val rorSettingsSourcesConfig = settings.settingsSource.copy(settingsFile = RorSettingsFile(esConfig / "readonlyrest.yml")) + settings.copy(settingsSource = rorSettingsSourcesConfig) + case Left(error) => + throw new IllegalStateException(s"Cannot create EsConfigBasedRorSettings: ${error.show}") + } + } + + private lazy val indexRorSettings = rorSettingsFromUnsafe( + """ + |readonlyrest: + | access_control_rules: + | - name: test_block + | type: allow + | auth_key: admin:container + |""".stripMargin + ) + + private lazy val updatedRorSettings = rorSettingsFromUnsafe( + """ + |readonlyrest: + | access_control_rules: + | + | - name: test_block + | type: allow + | auth_key: admin:container + | + | - name: test_block_2 + | type: allow + | auth_key: dev:container + | + |""".stripMargin + ) + +} diff --git a/core/src/test/scala/tech/beshu/ror/unit/boot/ReadonlyRestStartingTests.scala b/core/src/test/scala/tech/beshu/ror/unit/boot/ReadonlyRestStartingTests.scala index 1698d41a50..ced3cb4b90 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/boot/ReadonlyRestStartingTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/boot/ReadonlyRestStartingTests.scala @@ -16,19 +16,23 @@ */ package tech.beshu.ror.unit.boot +import better.files.File import cats.data.NonEmptyList -import cats.effect.Resource import cats.implicits.* import eu.timepit.refined.types.string.NonEmptyString +import io.circe.Json import io.lemonlabs.uri.Uri import monix.eval.Task import monix.execution.Scheduler.Implicits.global +import org.apache.commons.text.StringEscapeUtils.escapeJava import org.scalamock.scalatest.MockFactory import org.scalatest.concurrent.Eventually import org.scalatest.matchers.should.Matchers.* import org.scalatest.time.{Millis, Seconds, Span} import org.scalatest.wordspec.AnyWordSpec import org.scalatest.{EitherValues, Inside, OptionValues} +import squants.information.Bytes +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.AccessControlList import tech.beshu.ror.accesscontrol.AccessControlList.AccessControlStaticContext import tech.beshu.ror.accesscontrol.audit.AuditingTool @@ -39,21 +43,25 @@ import tech.beshu.ror.accesscontrol.blocks.definitions.{ExternalAuthenticationSe import tech.beshu.ror.accesscontrol.blocks.mocks.MocksProvider.{ExternalAuthenticationServiceMock, ExternalAuthorizationServiceMock, LdapServiceMock} import tech.beshu.ror.accesscontrol.domain.* import tech.beshu.ror.accesscontrol.domain.AuditCluster.{AuditClusterNode, ClusterMode} -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError -import tech.beshu.ror.accesscontrol.factory.RawRorConfigBasedCoreFactory.CoreCreationError.Reason.Message -import tech.beshu.ror.accesscontrol.factory.{Core, CoreFactory} +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError +import tech.beshu.ror.accesscontrol.factory.RawRorSettingsBasedCoreFactory.CoreCreationError.Reason.Message +import tech.beshu.ror.accesscontrol.factory.RorDependencies.NoOpImpersonationWarningsReader +import tech.beshu.ror.accesscontrol.factory.{Core, CoreFactory, RorDependencies} import tech.beshu.ror.accesscontrol.logging.AccessControlListLoggingDecorator +import tech.beshu.ror.boot.ReadonlyRest import tech.beshu.ror.boot.ReadonlyRest.StartingFailure -import tech.beshu.ror.boot.RorInstance.{IndexConfigInvalidationError, TestConfig} -import tech.beshu.ror.boot.{ReadonlyRest, RorInstance} -import tech.beshu.ror.configuration.RorConfig.NoOpImpersonationWarningsReader -import tech.beshu.ror.configuration.index.SavingIndexConfigError -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig, RorConfig} +import tech.beshu.ror.boot.RorInstance.{IndexSettingsInvalidationError, TestSettings} import tech.beshu.ror.es.DataStreamService.CreationResult.{Acknowledged, NotAcknowledged} import tech.beshu.ror.es.DataStreamService.{CreationResult, DataStreamSettings} -import tech.beshu.ror.es.IndexJsonContentService.{CannotReachContentSource, CannotWriteToIndex, ContentNotFound, WriteError} -import tech.beshu.ror.es.{DataStreamBasedAuditSinkService, DataStreamService, EsEnv, IndexJsonContentService} +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.es.{DataStreamBasedAuditSinkService, DataStreamService, EsEnv, IndexDocumentManager} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError +import tech.beshu.ror.settings.ror.RawRorSettings +import tech.beshu.ror.settings.ror.source.IndexSettingsSource.SavingError.CannotSaveSettings +import tech.beshu.ror.settings.ror.source.ReadWriteSettingsSource.SettingsSavingError import tech.beshu.ror.syntax.* +import tech.beshu.ror.unit.utils.WithReadonlyrestBootSupport import tech.beshu.ror.utils.DurationOps.* import tech.beshu.ror.utils.TestsPropertiesProvider import tech.beshu.ror.utils.TestsUtils.* @@ -67,6 +75,7 @@ import scala.language.postfixOps class ReadonlyRestStartingTests extends AnyWordSpec + with WithReadonlyrestBootSupport with Inside with OptionValues with EitherValues with MockFactory with Eventually { @@ -78,28 +87,31 @@ class ReadonlyRestStartingTests "A ReadonlyREST core" should { "support the main engine" should { "be loaded from file" when { - "index is not available but file config is provided" in withReadonlyRest({ - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "1") - .repeated(1) - .returns(Task.now(Left(CannotReachContentSource))) - - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) - - val coreFactory = mockCoreFactory(mock[CoreFactory], "/boot_tests/no_index_config_file_config_provided/readonlyrest.yml") - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, "/boot_tests/no_index_config_file_config_provided/") + "index is not available but file settings is provided" in withReadonlyRest({ + val resourcePath = "/boot_tests/no_index_settings_file_settings_provided" + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettingsReturnsError(mockedIndexDocumentManager, error = IndexNotFound) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = IndexNotFound) + val coreFactory = mockCoreFactory(mock[CoreFactory], s"$resourcePath/readonlyrest.yml") + + implicit val systemContext: SystemContext = createSystemContext() + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcePath) + ) }) { rorInstance => val acl = rorInstance.engines.value.mainEngine.core.accessControl acl shouldBe a[AccessControlListLoggingDecorator] acl.asInstanceOf[AccessControlListLoggingDecorator].underlying shouldBe a[EnabledAcl] } "file loading is forced in elasticsearch.yml" in withReadonlyRest({ - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) - - val coreFactory = mockCoreFactory(mock[CoreFactory], "/boot_tests/forced_file_loading/readonlyrest.yml") - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, "/boot_tests/forced_file_loading/") + val resourcePath = "/boot_tests/forced_file_loading/" + val coreFactory = mockCoreFactory(mock[CoreFactory], s"$resourcePath/readonlyrest.yml") + implicit val systemContext: SystemContext = createSystemContext() + ( + readonlyRestBoot(coreFactory, mock[IndexDocumentManager]), + forceCreateEsConfigBasedRorSettings(resourcePath) + ) }) { rorInstance => val acl = rorInstance.engines.value.mainEngine.core.accessControl acl shouldBe a[AccessControlListLoggingDecorator] @@ -107,32 +119,35 @@ class ReadonlyRestStartingTests } } "be loaded from index" when { - "index is available and file config is provided" in withReadonlyRest({ - val resourcesPath = "/boot_tests/index_config_available_file_config_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) - - val coreFactory = mockCoreFactory(mock[CoreFactory], resourcesPath + indexConfigFile) - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath) + "index is available and file settings is provided" in withReadonlyRest({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_provided/" + val indexSettingsFile = "readonlyrest_index.yml" + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) + val coreFactory = mockCoreFactory(mock[CoreFactory], resourcesPath + indexSettingsFile) + implicit val systemContext: SystemContext = createSystemContext() + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => val acl = rorInstance.engines.value.mainEngine.core.accessControl acl shouldBe a[AccessControlListLoggingDecorator] acl.asInstanceOf[AccessControlListLoggingDecorator].underlying shouldBe a[EnabledAcl] } - "index is available and file config is not provided" in withReadonlyRest({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) - - val coreFactory = mockCoreFactory(mock[CoreFactory], resourcesPath + indexConfigFile) - - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath) + "index is available and file settings is not provided" in withReadonlyRest({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) + val coreFactory = mockCoreFactory(mock[CoreFactory], resourcesPath + indexSettingsFile) + implicit val systemContext: SystemContext = createSystemContext() + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => val acl = rorInstance.engines.value.mainEngine.core.accessControl acl shouldBe a[AccessControlListLoggingDecorator] @@ -140,64 +155,73 @@ class ReadonlyRestStartingTests } } "be able to be reloaded" when { - "new config is different than old one" in withReadonlyRest({ - val resourcesPath = "/boot_tests/config_reloading/" - val initialIndexConfigFile = "readonlyrest_initial.yml" - val newIndexConfigFile = "readonlyrest_first.yml" + "new settings are different than old one" in withReadonlyRest({ + val resourcesPath = "/boot_tests/settings_reloading/" + val initialIndexSettingsFile = "readonlyrest_initial.yml" + val newIndexSettingsFile = "readonlyrest_first.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + initialIndexConfigFile) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + initialIndexSettingsFile) + mockSavingMainSettings(mockedIndexDocumentManager, resourcesPath + newIndexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + initialIndexConfigFile) - mockCoreFactory(coreFactory, resourcesPath + newIndexConfigFile) - mockIndexJsonContentManagerSaveCall(mockedIndexJsonContentManager, resourcesPath + newIndexConfigFile) - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) + mockCoreFactory(coreFactory, resourcesPath + initialIndexSettingsFile) + mockCoreFactory(coreFactory, resourcesPath + newIndexSettingsFile) - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(0 seconds)) + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(0 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => val mainEngine = rorInstance.engines.value.mainEngine mainEngine.core.accessControl shouldBe a[AccessControlListLoggingDecorator] mainEngine.core.accessControl.asInstanceOf[AccessControlListLoggingDecorator].underlying shouldBe a[EnabledAcl] val reload1Result = rorInstance - .forceReloadAndSave(rorConfigFromResource("/boot_tests/config_reloading/readonlyrest_first.yml"))(newRequestId()) + .forceReloadAndSave(rorSettingsFromResource("/boot_tests/settings_reloading/readonlyrest_first.yml"))(newRequestId()) .runSyncUnsafe() reload1Result should be(Right(())) assert(mainEngine != rorInstance.engines.value.mainEngine, "Engine was not reloaded") } "two parallel force reloads are invoked" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/config_reloading/" - val initialIndexConfigFile = "readonlyrest_initial.yml" - val firstNewIndexConfigFile = "readonlyrest_first.yml" - val secondNewIndexConfigFile = "readonlyrest_second.yml" + val resourcesPath = "/boot_tests/settings_reloading/" + val initialIndexSettingsFile = "readonlyrest_initial.yml" + val firstNewIndexSettingsFile = "readonlyrest_first.yml" + val secondNewIndexSettingsFile = "readonlyrest_second.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + initialIndexConfigFile) - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + initialIndexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + initialIndexConfigFile) - mockCoreFactory(coreFactory, resourcesPath + firstNewIndexConfigFile) - mockCoreFactory(coreFactory, resourcesPath + secondNewIndexConfigFile, + mockCoreFactory(coreFactory, resourcesPath + initialIndexSettingsFile) + mockCoreFactory(coreFactory, resourcesPath + firstNewIndexSettingsFile) + mockCoreFactory(coreFactory, resourcesPath + secondNewIndexSettingsFile, createCoreResult = Task .sleep(100 millis) - .map(_ => Right(Core(mockEnabledAccessControl, RorConfig.disabled))) // very long creation + .map(_ => Right(Core(mockEnabledAccessControl, RorDependencies.noOp, None))) // very long creation ) - mockIndexJsonContentManagerSaveCall( - mockedIndexJsonContentManager, - resourcesPath + firstNewIndexConfigFile, + mockSavingMainSettings( + mockedIndexDocumentManager, + resourcesPath + firstNewIndexSettingsFile, Task.sleep(500 millis).map(_ => Right(())) // very long saving ) - val readonlyrest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(0 seconds)) - (readonlyrest, mockedIndexJsonContentManager) - }) { case (rorInstance, mockedIndexJsonContentManager) => - val resourcesPath = "/boot_tests/config_reloading/" - val firstNewIndexConfigFile = "readonlyrest_first.yml" - val secondNewIndexConfigFile = "readonlyrest_second.yml" + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(0 seconds)) + + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + mockedIndexDocumentManager + ) + }) { case (rorInstance, mockedIndexDocumentManager) => + val resourcesPath = "/boot_tests/settings_reloading/" + val firstNewIndexSettingsFile = "readonlyrest_first.yml" + val secondNewIndexSettingsFile = "readonlyrest_second.yml" eventually { rorInstance.engines.value.mainEngine.core.accessControl @@ -206,16 +230,16 @@ class ReadonlyRestStartingTests val results = Task .parSequence(List( rorInstance - .forceReloadAndSave(rorConfigFromResource(resourcesPath + firstNewIndexConfigFile))(newRequestId()) + .forceReloadAndSave(rorSettingsFromResource(resourcesPath + firstNewIndexSettingsFile))(newRequestId()) .map { result => // schedule after first finish - mockIndexJsonContentManagerSaveCall(mockedIndexJsonContentManager, resourcesPath + secondNewIndexConfigFile) + mockSavingMainSettings(mockedIndexDocumentManager, resourcesPath + secondNewIndexSettingsFile) result }, Task .sleep(200 millis) .flatMap { _ => - rorInstance.forceReloadAndSave(rorConfigFromResource(resourcesPath + secondNewIndexConfigFile))(newRequestId()) + rorInstance.forceReloadAndSave(rorSettingsFromResource(resourcesPath + secondNewIndexSettingsFile))(newRequestId()) } )) .runSyncUnsafe() @@ -224,22 +248,28 @@ class ReadonlyRestStartingTests results should be(Right(List((), ()))) } } - "be reloaded if index config changes" in withReadonlyRest({ - val resourcesPath = "/boot_tests/index_config_reloading/" - val originIndexConfigFile = "readonlyrest.yml" - val updatedIndexConfigFile = "updated_readonlyrest.yml" + "be reloaded if index settings change" in withReadonlyRest({ + val resourcesPath = "/boot_tests/index_settings_reloading/" + val originIndexSettingsFile = "readonlyrest.yml" + val updatedIndexSettingsFile = "updated_readonlyrest.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] + val mockedIndexDocumentManager = mock[IndexDocumentManager] val coreFactory = mock[CoreFactory] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + originIndexConfigFile, repeatedCount = 1) - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) - mockCoreFactory(coreFactory, resourcesPath + originIndexConfigFile, mockDisabledAccessControl) + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + originIndexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) + mockCoreFactory(coreFactory, resourcesPath + originIndexSettingsFile, mockDisabledAccessControl) - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + updatedIndexConfigFile) - mockCoreFactory(coreFactory, resourcesPath + updatedIndexConfigFile) + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + updatedIndexSettingsFile, AttemptCount.AnyNumberOfTimes) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound, AttemptCount.AnyNumberOfTimes) + mockCoreFactory(coreFactory, resourcesPath + updatedIndexSettingsFile) - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(2 seconds)) + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(2 seconds)) + + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => val acl = rorInstance.engines.value.mainEngine.core.accessControl acl shouldBe a[AccessControlListLoggingDecorator] @@ -254,181 +284,194 @@ class ReadonlyRestStartingTests acl2.asInstanceOf[AccessControlListLoggingDecorator].underlying shouldBe a[EnabledAcl] } "failed to load" when { - "force load from file is set and config is malformed" in { - val readonlyRest = readonlyRestBoot(mock[CoreFactory], mock[IndexJsonContentService], "/boot_tests/forced_file_loading_malformed_config/") - - val result = readonlyRest.start().runSyncUnsafe() + "force load from file is set and settings file is malformed yaml" in { + val resourcesPath = "/boot_tests/forced_file_loading_malformed_settings/" + implicit val systemContext: SystemContext = createSystemContext() + val result = createEsConfigBasedRorSettings(resourcesPath) - inside(result) { case Left(failure) => - failure.message should startWith("Settings file is malformed:") + inside(result) { case Left(LoadingError.MalformedSettings(_, message)) => + message should startWith("Cannot parse file") } } - "force load from file is set and config cannot be loaded" in { - val coreFactory = mockFailedCoreFactory(mock[CoreFactory], "/boot_tests/forced_file_loading_bad_config/readonlyrest.yml") - val readonlyRest = readonlyRestBoot( - factory = coreFactory, - indexJsonContentService = mockIndexJsonContentManagerSourceOfCallTestConfig(mock[IndexJsonContentService]), - configPath = "/boot_tests/forced_file_loading_bad_config/" - ) + "force load from file is set and core cannot be loaded" in { + val resourcesPath = "/boot_tests/forced_file_loading_bad_settings/" + val coreFactory = mockFailedCoreFactory(mock[CoreFactory], resourcesPath + "readonlyrest.yml") + + implicit val systemContext: SystemContext = createSystemContext() + val readonlyRest = readonlyRestBoot(coreFactory, mock[IndexDocumentManager]) + val esConfigBasedRorSettings = forceCreateEsConfigBasedRorSettings(resourcesPath) - val result = readonlyRest.start().runSyncUnsafe() + val result = readonlyRest.start(esConfigBasedRorSettings).runSyncUnsafe() inside(result) { case Left(failure) => failure.message shouldBe "Errors:\nfailed" } } - "index config doesn't exist and file config is malformed" in { - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "1") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + "index settings don't exist and settings file is malformed yaml" in { + val resourcesPath = "/boot_tests/index_settings_not_exists_malformed_file_settings/" + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettingsReturnsError(mockedIndexDocumentManager, error = IndexNotFound) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = IndexNotFound) + val coreFactory = mockFailedCoreFactory(mock[CoreFactory], resourcesPath + "readonlyrest.yml") + + implicit val systemContext: SystemContext = createSystemContext() - val readonlyRest = readonlyRestBoot(mock[CoreFactory], mockedIndexJsonContentManager, "/boot_tests/index_config_not_exists_malformed_file_config/") + val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexDocumentManager) + val esConfigBasedRorSettings = forceCreateEsConfigBasedRorSettings(resourcesPath) - val result = readonlyRest.start().runSyncUnsafe() + val result = readonlyRest.start(esConfigBasedRorSettings).runSyncUnsafe() inside(result) { case Left(failure) => - failure.message should startWith("Settings content is malformed.") + failure.message shouldBe "Errors:\nfailed" } } - "index config doesn't exist and file config cannot be loaded" in { - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "1") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) + "index settings don't exist and core cannot be loaded" in { + val resourcePath = "/boot_tests/index_settings_not_exists_bad_file_settings/" + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) + + val coreFactory = mockFailedCoreFactory(mock[CoreFactory], resourcePath + "readonlyrest.yml") + implicit val systemContext: SystemContext = createSystemContext() - val coreFactory = mockFailedCoreFactory(mock[CoreFactory], "/boot_tests/index_config_not_exists_bad_file_config/readonlyrest.yml") - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, "/boot_tests/index_config_not_exists_bad_file_config/") + val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexDocumentManager) + val esConfigBasedRorSettings = forceCreateEsConfigBasedRorSettings(resourcePath) - val result = readonlyRest.start().runSyncUnsafe() + val result = readonlyRest.start(esConfigBasedRorSettings).runSyncUnsafe() inside(result) { case Left(failure) => failure.message shouldBe "Errors:\nfailed" } } - "index config is malformed" in { - val resourcesPath = "/boot_tests/malformed_index_config/" - val indexConfigFile = "readonlyrest_index.yml" + "index settings are malformed" in { + val resourcesPath = "/boot_tests/malformed_index_settings/" + val indexSettingsFile = "readonlyrest_index.yml" + + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) + val coreFactory = mockFailedCoreFactory(mock[CoreFactory], resourcesPath + "readonlyrest.yml") - val readonlyRest = readonlyRestBoot(mock[CoreFactory], mockedIndexJsonContentManager, resourcesPath) + implicit val systemContext: SystemContext = createSystemContext() - val result = readonlyRest.start().runSyncUnsafe() + val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexDocumentManager) + val esConfigBasedRorSettings = forceCreateEsConfigBasedRorSettings(resourcesPath) + + val result = readonlyRest.start(esConfigBasedRorSettings).runSyncUnsafe() inside(result) { case Left(failure) => - failure.message should startWith("Settings content is malformed.") + failure.message shouldBe "Errors:\nfailed" } } - "index config cannot be loaded" in { - val resourcesPath = "/boot_tests/bad_index_config/" - val indexConfigFile = "readonlyrest_index.yml" + "index settings cannot be loaded" in { + val resourcesPath = "/boot_tests/bad_index_settings/" + val indexSettingsFile = "readonlyrest_index.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) - val coreFactory = mockFailedCoreFactory(mock[CoreFactory], resourcesPath + indexConfigFile) - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath) + val coreFactory = mockFailedCoreFactory(mock[CoreFactory], resourcesPath + indexSettingsFile) + implicit val systemContext: SystemContext = createSystemContext() + val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexDocumentManager) + val esConfigBasedRorSettings = forceCreateEsConfigBasedRorSettings(resourcesPath) - val result = readonlyRest.start().runSyncUnsafe() + val result = readonlyRest.start(esConfigBasedRorSettings).runSyncUnsafe() inside(result) { case Left(failure) => failure.message shouldBe "Errors:\nfailed" } } "ROR SSL (in elasticsearch.yml) is tried to be used when XPack Security is enabled" in { - val readonlyRest = readonlyRestBoot(mock[CoreFactory], mock[IndexJsonContentService], "/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/") - - val result = readonlyRest.start().runSyncUnsafe() + implicit val systemContext: SystemContext = createSystemContext() + val result = createEsConfigBasedRorSettings("/boot_tests/ror_ssl_declared_in_es_file_xpack_security_enabled/") - inside(result) { case Left(failure) => - failure.message should be("Cannot use ROR SSL configuration when XPack Security is enabled") + inside(result) { + case Left(LoadingError.MalformedSettings(_, "Cannot use ROR SSL when XPack Security is enabled")) => } } "ROR SSL (in readonlyrest.yml) is tried to be used when XPack Security is enabled" in { - val readonlyRest = readonlyRestBoot(mock[CoreFactory], mock[IndexJsonContentService], "/boot_tests/ror_ssl_declared_in_readonlyrest_file_xpack_security_enabled/") - - val result = readonlyRest.start().runSyncUnsafe() + implicit val systemContext: SystemContext = createSystemContext() + val result = createEsConfigBasedRorSettings("/boot_tests/ror_ssl_declared_in_readonlyrest_file_xpack_security_enabled/") - inside(result) { case Left(failure) => - failure.message should be("Cannot use ROR SSL configuration when XPack Security is enabled") + inside(result) { + case Left(LoadingError.MalformedSettings(_, "Cannot use ROR SSL when XPack Security is enabled")) => } } "ROR FIPS SSL is tried to be used when XPack Security is enabled" in { - val readonlyRest = readonlyRestBoot(mock[CoreFactory], mock[IndexJsonContentService], "/boot_tests/ror_fisb_ssl_declared_in_readonlyrest_file_xpack_security_enabled/") + implicit val systemContext: SystemContext = createSystemContext() + val result = createEsConfigBasedRorSettings("/boot_tests/ror_fisb_ssl_declared_in_readonlyrest_file_xpack_security_enabled/") - val result = readonlyRest.start().runSyncUnsafe() - - inside(result) { case Left(failure) => - failure.message should be("Cannot use ROR FIBS configuration when XPack Security is enabled") + inside(result) { + case Left(LoadingError.MalformedSettings(_, "Cannot use ROR SSL when XPack Security is enabled")) => } } } } "support the test engine" which { "can be initialized" when { - "there is no config in index" in withReadonlyRest({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) + "there is no settings in index" in withReadonlyRest({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(5 seconds)) - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(5 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) } - "there is some config stored in index" should { + "there is some settings stored in index" should { "load test engine as active" when { - "config is still valid" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - - lazy val expirationTimestamp = testClock.instant().plusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp.toString, - "auth_services_mocks" -> configuredAuthServicesMocksJson, - ) - ))) + "settings are still valid" in withReadonlyRestExt({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" + val expirationTimestamp = testClock.instant().plusSeconds(100) + + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettings( + mockedIndexDocumentManager, + circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp.toString}", + | "auth_services_mocks": $configuredAuthServicesMocksJson + |}""".stripMargin + ) + ) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(10 seconds)) - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(10 seconds)) - (readonlyRest, expirationTimestamp) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + expirationTimestamp + ) }) { case (rorInstance, expirationTimestamp) => rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Present( - config = RorConfig.disabled, - rawConfig = testConfig1, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Present( + dependencies = RorDependencies.noOp, + rawSettings = testSettings1, configuredTtl = (100 seconds).toRefinedPositiveUnsafe, validTo = expirationTimestamp ) @@ -460,35 +503,38 @@ class ReadonlyRestStartingTests } "load test engine as invalidated" when { "the expiration timestamp exceeded" in withReadonlyRest({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" lazy val expirationTimestamp = testClock.instant().minusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettings( + mockedIndexDocumentManager, + circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp.toString}", + | "auth_services_mocks": $configuredAuthServicesMocksJson + |}""".stripMargin + ) + ) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(10 seconds)) + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(10 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Invalidated( - recent = testConfig1, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Invalidated( + recent = testSettings1, configuredTtl = (100 seconds).toRefinedPositiveUnsafe ) ) @@ -497,88 +543,94 @@ class ReadonlyRestStartingTests } "index is not accessible" should { "fallback to not configured" in withReadonlyRest({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(CannotReachContentSource))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(5 seconds)) + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(5 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) } } "settings structure is not valid" should { "fallback to not configured" in withReadonlyRest({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" lazy val expirationTimestamp = testClock.instant().minusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> "malformed_config", // malformed ror config - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettings( + mockedIndexDocumentManager, + circeJsonFrom( + s"""{ + | "settings": "malformed_settings", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp.toString}", + | "auth_services_mocks": $configuredAuthServicesMocksJson + |}""".stripMargin + ) + ) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(5 seconds)) + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(5 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) } } "settings structure is valid, rule is malformed and cannot start engine" should { - "fallback to invalidated config" in withReadonlyRest({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfigMalformed.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> testClock.instant().plusSeconds(100).toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + "fallback to invalidated settings" in withReadonlyRest({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" + + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettings( + mockedIndexDocumentManager, + circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettingsMalformed.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${testClock.instant().plusSeconds(100).toString}", + | "auth_services_mocks": $configuredAuthServicesMocksJson + |}""".stripMargin + ) + ) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockFailedCoreFactory(coreFactory, testConfigMalformed) + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockFailedCoreFactory(coreFactory, testSettingsMalformed) - readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(5 seconds)) + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(5 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath) + ) }) { rorInstance => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Invalidated( - recent = testConfigMalformed, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Invalidated( + recent = testSettingsMalformed, configuredTtl = (100 seconds).toRefinedPositiveUnsafe ) ) @@ -587,529 +639,534 @@ class ReadonlyRestStartingTests } "can be loaded on demand" when { "there is no previous engine" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(10 seconds)) - (readonlyRest, mockedIndexJsonContentManager) - }) { case (rorInstance, mockedIndexJsonContentManager) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(10 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + mockedIndexDocumentManager + ) + }) { case (rorInstance, mockedIndexDocumentManager) => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("60000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("60000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(1) .returns(Task.now(Right(()))) val testEngineReloadResult = rorInstance - .forceReloadTestConfigEngine(testConfig1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult.value shouldBe a[TestConfig.Present] + testEngineReloadResult.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - val testEngineConfig = rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() - testEngineConfig shouldBe a[TestConfig.Present] - Option(testEngineConfig.asInstanceOf[TestConfig.Present]).map(i => (i.rawConfig, i.configuredTtl.value)) should be { - (testConfig1, 1 minute).some + val testSettingsEngine = rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() + testSettingsEngine shouldBe a[TestSettings.Present] + Option(testSettingsEngine.asInstanceOf[TestSettings.Present]).map(i => (i.rawSettings, i.configuredTtl.value)) should be { + (testSettings1, 1 minute).some } } "there is previous engine" when { - "same config and ttl" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) + "same settings and ttl" in withReadonlyRestExt({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(10 seconds)) - (readonlyRest, mockedIndexJsonContentManager) - }) { case (rorInstance, mockedIndexJsonContentManager) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(10 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + mockedIndexDocumentManager + ) + }) { case (rorInstance, mockedIndexDocumentManager) => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("60000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("60000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(2) .returns(Task.now(Right(()))) val testEngineReloadResult1stAttempt = rorInstance - .forceReloadTestConfigEngine(testConfig1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult1stAttempt.value shouldBe a[TestConfig.Present] + testEngineReloadResult1stAttempt.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - val testEngineConfig = rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() - testEngineConfig shouldBe a[TestConfig.Present] - Option(testEngineConfig.asInstanceOf[TestConfig.Present]).map(i => (i.rawConfig, i.configuredTtl.value)) should be { - (testConfig1, 1 minute).some + val testSettingsEngine = rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() + testSettingsEngine shouldBe a[TestSettings.Present] + Option(testSettingsEngine.asInstanceOf[TestSettings.Present]).map(i => (i.rawSettings, i.configuredTtl.value)) should be { + (testSettings1, 1 minute).some } - val testEngine1Expiration = testEngineConfig.asInstanceOf[TestConfig.Present].validTo + val testEngine1Expiration = testSettingsEngine.asInstanceOf[TestSettings.Present].validTo val testEngineReloadResult2ndAttempt = rorInstance - .forceReloadTestConfigEngine(testConfig1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult2ndAttempt.value shouldBe a[TestConfig.Present] + testEngineReloadResult2ndAttempt.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - val testEngineConfigAfterReload = rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() - testEngineConfigAfterReload shouldBe a[TestConfig.Present] - Option(testEngineConfigAfterReload.asInstanceOf[TestConfig.Present]).map(i => (i.rawConfig, i.configuredTtl.value)) should be { - (testConfig1, 1 minute).some + val testSettingsEngineAfterReload = rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() + testSettingsEngineAfterReload shouldBe a[TestSettings.Present] + Option(testSettingsEngineAfterReload.asInstanceOf[TestSettings.Present]).map(i => (i.rawSettings, i.configuredTtl.value)) should be { + (testSettings1, 1 minute).some } - val testEngine2Expiration = testEngineConfigAfterReload.asInstanceOf[TestConfig.Present].validTo + val testEngine2Expiration = testSettingsEngineAfterReload.asInstanceOf[TestSettings.Present].validTo testEngine2Expiration.isAfter(testEngine1Expiration) should be(true) } "different ttl" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(10 seconds)) - (readonlyRest, mockedIndexJsonContentManager) - }) { case (rorInstance, mockedIndexJsonContentManager) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(10 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + mockedIndexDocumentManager + ) + }) { case (rorInstance, mockedIndexDocumentManager) => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("600000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("600000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(1) .returns(Task.now(Right(()))) val testEngineReloadResult1stAttempt = rorInstance - .forceReloadTestConfigEngine(testConfig1, (10 minute).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings1, (10 minute).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult1stAttempt.value shouldBe a[TestConfig.Present] + testEngineReloadResult1stAttempt.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - val testEngineConfig = rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() - testEngineConfig shouldBe a[TestConfig.Present] - Option(testEngineConfig.asInstanceOf[TestConfig.Present]) - .map(i => (i.rawConfig, i.configuredTtl.value)) should be((testConfig1, 10 minute).some) + val testSettingsEngine = rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() + testSettingsEngine shouldBe a[TestSettings.Present] + Option(testSettingsEngine.asInstanceOf[TestSettings.Present]) + .map(i => (i.rawSettings, i.configuredTtl.value)) should be((testSettings1, 10 minute).some) - val testEngine1Expiration = testEngineConfig.asInstanceOf[TestConfig.Present].validTo + val testEngine1Expiration = testSettingsEngine.asInstanceOf[TestSettings.Present].validTo - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("300000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("300000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(1) .returns(Task.now(Right(()))) val testEngineReloadResult2ndAttempt = rorInstance - .forceReloadTestConfigEngine(testConfig1, (5 minute).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings1, (5 minute).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult2ndAttempt.value shouldBe a[TestConfig.Present] + testEngineReloadResult2ndAttempt.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - val testEngineConfigAfterReload = rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() - testEngineConfigAfterReload shouldBe a[TestConfig.Present] - Option(testEngineConfigAfterReload.asInstanceOf[TestConfig.Present]) - .map(i => (i.rawConfig, i.configuredTtl.value)) should be((testConfig1, 5 minute).some) + val testSettingsEngineAfterReload = rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() + testSettingsEngineAfterReload shouldBe a[TestSettings.Present] + Option(testSettingsEngineAfterReload.asInstanceOf[TestSettings.Present]) + .map(i => (i.rawSettings, i.configuredTtl.value)) should be((testSettings1, 5 minute).some) - val testEngine2Expiration = testEngineConfigAfterReload.asInstanceOf[TestConfig.Present].validTo + val testEngine2Expiration = testSettingsEngineAfterReload.asInstanceOf[TestSettings.Present].validTo testEngine2Expiration.isAfter(testEngine1Expiration) should be(false) } } - "different config is being loaded" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) + "different settings is being loaded" in withReadonlyRestExt({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - mockCoreFactory(coreFactory, testConfig2, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(10 seconds)) - (readonlyRest, mockedIndexJsonContentManager) - }) { case (rorInstance, mockedIndexJsonContentManager) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + mockCoreFactory(coreFactory, testSettings2, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(10 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + mockedIndexDocumentManager + ) + }) { case (rorInstance, mockedIndexDocumentManager) => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("60000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("60000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(1) .returns(Task.now(Right(()))) val testEngineReloadResult1stAttempt = rorInstance - .forceReloadTestConfigEngine(testConfig1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult1stAttempt.value shouldBe a[TestConfig.Present] + testEngineReloadResult1stAttempt.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - val testEngineConfig = rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() - testEngineConfig shouldBe a[TestConfig.Present] - Option(testEngineConfig.asInstanceOf[TestConfig.Present]).map(i => (i.rawConfig, i.configuredTtl.value)) should be { - (testConfig1, 1 minute).some + val testSettingsEngine = rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() + testSettingsEngine shouldBe a[TestSettings.Present] + Option(testSettingsEngine.asInstanceOf[TestSettings.Present]).map(i => (i.rawSettings, i.configuredTtl.value)) should be { + (testSettings1, 1 minute).some } - val testEngine1Expiration = testEngineConfig.asInstanceOf[TestConfig.Present].validTo + val testEngine1Expiration = testSettingsEngine.asInstanceOf[TestSettings.Present].validTo - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig2.raw) && - content.get("expiration_ttl_millis").contains("120000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings2.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("120000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(1) .returns(Task.now(Right(()))) val testEngineReloadResult2ndAttempt = rorInstance - .forceReloadTestConfigEngine(testConfig2, (2 minutes).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings2, (2 minutes).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult2ndAttempt.value shouldBe a[TestConfig.Present] + testEngineReloadResult2ndAttempt.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - val testEngineConfigAfterReload = rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() - testEngineConfigAfterReload shouldBe a[TestConfig.Present] - Option(testEngineConfigAfterReload.asInstanceOf[TestConfig.Present]) - .map(i => (i.rawConfig, i.configuredTtl.value)) should be((testConfig2, 2 minutes).some) + val testSettingsEngineAfterReload = rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() + testSettingsEngineAfterReload shouldBe a[TestSettings.Present] + Option(testSettingsEngineAfterReload.asInstanceOf[TestSettings.Present]) + .map(i => (i.rawSettings, i.configuredTtl.value)) should be((testSettings2, 2 minutes).some) - val testEngine2Expiration = testEngineConfigAfterReload.asInstanceOf[TestConfig.Present].validTo + val testEngine2Expiration = testSettingsEngineAfterReload.asInstanceOf[TestSettings.Present].validTo testEngine2Expiration.isAfter(testEngine1Expiration) should be(true) } } - "can be reloaded if index config changes" when { - "new config and expiration time has not exceeded" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" + "can be reloaded if index settings changes" when { + "new settings and expiration time has not exceeded" in withReadonlyRestExt({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile, 2) - - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile, AttemptCount.Exact(2)) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(2 seconds)) - (readonlyRest, mockedIndexJsonContentManager) - }) { case (rorInstance, mockedIndexJsonContentManager) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(2 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + mockedIndexDocumentManager + ) + }) { case (rorInstance, mockedIndexDocumentManager) => lazy val expirationTimestamp = testClock.instant().plusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) + (mockedIndexDocumentManager.documentAsJson _) .expects(fullIndexName(".readonlyrest"), "2") .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + .returns(Task.now(Right(circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp.toString}", + | "auth_services_mocks": $notConfiguredAuthServicesMocksJson + |}""".stripMargin + )))) rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) Task.sleep(5 seconds).runSyncUnsafe() rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Present( - config = RorConfig.disabled, - rawConfig = testConfig1, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Present( + dependencies = RorDependencies.noOp, + rawSettings = testSettings1, configuredTtl = (100 seconds).toRefinedPositiveUnsafe, validTo = expirationTimestamp ) ) } - "same config and the ttl has changed" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile, 2) - + "same settings and the ttl has changed" in withReadonlyRestExt({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" lazy val expirationTimestamp = testClock.instant().plusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) - + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile, AttemptCount.Exact(2)) + mockGettingTestSettings( + mockedIndexDocumentManager, + circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp.toString}", + | "auth_services_mocks": $notConfiguredAuthServicesMocksJson + |}""".stripMargin + ) + ) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(2 seconds)) - (readonlyRest, (mockedIndexJsonContentManager, expirationTimestamp)) - }) { case (rorInstance, (mockedIndexJsonContentManager, expirationTimestamp)) => - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Present( - config = RorConfig.disabled, - rawConfig = testConfig1, + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(2 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + (mockedIndexDocumentManager, expirationTimestamp) + ) + }) { case (rorInstance, (mockedIndexDocumentManager, expirationTimestamp)) => + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Present( + dependencies = RorDependencies.noOp, + rawSettings = testSettings1, configuredTtl = (100 seconds).toRefinedPositiveUnsafe, validTo = expirationTimestamp ) ) val expirationTimestamp2 = testClock.instant().plusSeconds(200) - (mockedIndexJsonContentManager.sourceOf _) + (mockedIndexDocumentManager.documentAsJson _) .expects(fullIndexName(".readonlyrest"), "2") .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "200000", - "expiration_timestamp" -> expirationTimestamp2.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + .returns(Task.now(Right(circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "200000", + | "expiration_timestamp": "${expirationTimestamp2.toString}", + | "auth_services_mocks": $notConfiguredAuthServicesMocksJson + |}""".stripMargin + )))) Task.sleep(5 seconds).runSyncUnsafe() rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Present( - config = RorConfig.disabled, - rawConfig = testConfig1, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Present( + dependencies = RorDependencies.noOp, + rawSettings = testSettings1, configuredTtl = (200 seconds).toRefinedPositiveUnsafe, validTo = expirationTimestamp2 ) ) } - "same config and the expiration time has changed" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile, 2) - + "same settings and the expiration time has changed" in withReadonlyRestExt({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" lazy val expirationTimestamp = testClock.instant().plusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile, AttemptCount.Exact(2)) + mockGettingTestSettings( + mockedIndexDocumentManager, + circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp.toString}", + | "auth_services_mocks": $notConfiguredAuthServicesMocksJson + |}""".stripMargin + ) + ) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(2 seconds)) - (readonlyRest, (mockedIndexJsonContentManager, expirationTimestamp)) - }) { case (rorInstance, (mockedIndexJsonContentManager, expirationTimestamp)) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(2 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + (mockedIndexDocumentManager, expirationTimestamp) + ) + }) { case (rorInstance, (mockedIndexDocumentManager, expirationTimestamp)) => - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Present( - config = RorConfig.disabled, - rawConfig = testConfig1, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Present( + dependencies = RorDependencies.noOp, + rawSettings = testSettings1, configuredTtl = (100 seconds).toRefinedPositiveUnsafe, validTo = expirationTimestamp ) ) val expirationTimestamp2 = testClock.instant().plusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) + (mockedIndexDocumentManager.documentAsJson _) .expects(fullIndexName(".readonlyrest"), "2") .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp2.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + .returns(Task.now(Right(circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp2.toString}", + | "auth_services_mocks": $notConfiguredAuthServicesMocksJson + |}""".stripMargin + )))) Task.sleep(5 seconds).runSyncUnsafe() rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Present( - config = RorConfig.disabled, - rawConfig = testConfig1, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Present( + dependencies = RorDependencies.noOp, + rawSettings = testSettings1, configuredTtl = (100 seconds).toRefinedPositiveUnsafe, validTo = expirationTimestamp2 ) ) } - "new config and has already expired" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile, 2) - + "new settings and has already expired" in withReadonlyRestExt({ + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" lazy val expirationTimestamp = testClock.instant().plusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile, AttemptCount.Exact(2)) + mockGettingTestSettings( + mockedIndexDocumentManager, + circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp.toString}", + | "auth_services_mocks": $notConfiguredAuthServicesMocksJson + |}""".stripMargin + ) + ) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(2 seconds)) - (readonlyRest, (mockedIndexJsonContentManager, expirationTimestamp)) - }) { case (rorInstance, (mockedIndexJsonContentManager, expirationTimestamp)) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(2 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + (mockedIndexDocumentManager, expirationTimestamp) + ) + }) { case (rorInstance, (mockedIndexDocumentManager, expirationTimestamp)) => - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Present( - config = RorConfig.disabled, - rawConfig = testConfig1, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Present( + dependencies = RorDependencies.noOp, + rawSettings = testSettings1, configuredTtl = (100 seconds).toRefinedPositiveUnsafe, validTo = expirationTimestamp ) ) val expirationTimestamp2 = testClock.instant().minusSeconds(1) - (mockedIndexJsonContentManager.sourceOf _) + (mockedIndexDocumentManager.documentAsJson _) .expects(fullIndexName(".readonlyrest"), "2") .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig2.raw, - "expiration_ttl_millis" -> "200000", - "expiration_timestamp" -> expirationTimestamp2.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, - ) - ))) + .returns(Task.delay(Right(circeJsonFrom( + s"""{ + | "settings": "${escapeJava(testSettings2.rawYaml)}", + | "expiration_ttl_millis": "200000", + | "expiration_timestamp": "${expirationTimestamp2.toString}", + | "auth_services_mocks": $notConfiguredAuthServicesMocksJson + |}""".stripMargin + )))) Task.sleep(5 seconds).runSyncUnsafe() rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Invalidated( - recent = testConfig2, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Invalidated( + recent = testSettings2, configuredTtl = (200 seconds).toRefinedPositiveUnsafe ) ) @@ -1117,221 +1174,228 @@ class ReadonlyRestStartingTests } "should be automatically unloaded" when { "engine ttl has reached" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(0 seconds)) - (readonlyRest, mockedIndexJsonContentManager) - }) { case (rorInstance, mockedIndexJsonContentManager) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(0 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + mockedIndexDocumentManager + ) + }) { case (rorInstance, mockedIndexDocumentManager) => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("3000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("3000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(1) .returns(Task.now(Right(()))) val testEngineReloadResult = rorInstance - .forceReloadTestConfigEngine(testConfig1, (3 seconds).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings1, (3 seconds).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult.value shouldBe a[TestConfig.Present] + testEngineReloadResult.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] Task.sleep(5 seconds).runSyncUnsafe() rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Invalidated(testConfig1, (3 seconds).toRefinedPositiveUnsafe) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Invalidated(testSettings1, (3 seconds).toRefinedPositiveUnsafe) ) } } "can be invalidated by user" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Left(ContentNotFound))) + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettingsReturnsError(mockedIndexDocumentManager, error = DocumentNotFound) val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) - - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(10 seconds)) - (readonlyRest, mockedIndexJsonContentManager) - }) { case (rorInstance, mockedIndexJsonContentManager) => + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(10 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + mockedIndexDocumentManager) + }) { case (rorInstance, mockedIndexDocumentManager) => rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be(TestConfig.NotSet) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be(TestSettings.NotSet) - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("60000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("60000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(1) .returns(Task.now(Right(()))) val testEngineReloadResult = rorInstance - .forceReloadTestConfigEngine(testConfig1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) + .forceReloadTestSettingsEngine(testSettings1, (1 minute).toRefinedPositiveUnsafe)(newRequestId()) .runSyncUnsafe() - testEngineReloadResult.value shouldBe a[TestConfig.Present] + testEngineReloadResult.value shouldBe a[TestSettings.Present] rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() shouldBe a[TestConfig.Present] + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() shouldBe a[TestSettings.Present] - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("60000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("60000") && + document.hcursor.downField("expiration_timestamp").succeeded && + document.hcursor.downField("auth_services_mocks").succeeded } ) .repeated(1) .returns(Task.now(Right(()))) - rorInstance.invalidateTestConfigEngine()(newRequestId()).runSyncUnsafe() should be(Right(())) + rorInstance.invalidateTestSettingsEngine()(newRequestId()).runSyncUnsafe() should be(Right(())) rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Invalidated(recent = testConfig1, configuredTtl = (1 minute).toRefinedPositiveUnsafe) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Invalidated(recent = testSettings1, configuredTtl = (1 minute).toRefinedPositiveUnsafe) ) } "should return error for invalidation" when { - "cannot save invalidation timestamp in index" in withReadonlyRestExt({ - val resourcesPath = "/boot_tests/index_config_available_file_config_not_provided/" - val indexConfigFile = "readonlyrest_index.yml" - - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCall(mockedIndexJsonContentManager, resourcesPath + indexConfigFile) - - lazy val expirationTimestamp = testClock.instant().plusSeconds(100) - (mockedIndexJsonContentManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .repeated(1) - .returns(Task.now(Right( - Map( - "settings" -> testConfig1.raw, - "expiration_ttl_millis" -> "100000", - "expiration_timestamp" -> expirationTimestamp.toString, - "auth_services_mocks" -> notConfiguredAuthServicesMocksJson, + "cannot save invalidation timestamp in index" in withReadonlyRestExt( + { + val resourcesPath = "/boot_tests/index_settings_available_file_settings_not_provided/" + val indexSettingsFile = "readonlyrest_index.yml" + lazy val expirationTimestamp = testClock.instant().plusSeconds(100) + + val mockedIndexDocumentManager = mock[IndexDocumentManager] + mockGettingMainSettings(mockedIndexDocumentManager, resourcesPath + indexSettingsFile) + mockGettingTestSettings( + mockedIndexDocumentManager, + circeJsonFrom( + s""" + |{ + | "settings": "${escapeJava(testSettings1.rawYaml)}", + | "expiration_ttl_millis": "100000", + | "expiration_timestamp": "${expirationTimestamp.toString}", + | "auth_services_mocks": $notConfiguredAuthServicesMocksJson + |} + |""".stripMargin ) - ))) - - val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, resourcesPath + indexConfigFile) - mockCoreFactory(coreFactory, testConfig1, mockEnabledAccessControl, disabledRorConfig) + ) - val readonlyRest = readonlyRestBoot(coreFactory, mockedIndexJsonContentManager, resourcesPath, refreshInterval = Some(10 seconds)) - (readonlyRest, (mockedIndexJsonContentManager, expirationTimestamp)) - }) { case (rorInstance, (mockedIndexJsonContentManager, expirationTimestamp)) => + val coreFactory = mock[CoreFactory] + mockCoreFactory(coreFactory, resourcesPath + indexSettingsFile) + mockCoreFactory(coreFactory, testSettings1, mockEnabledAccessControl, RorDependencies.noOp, None) + + implicit val systemContext: SystemContext = createSystemContext(refreshInterval = Some(10 seconds)) + ( + readonlyRestBoot(coreFactory, mockedIndexDocumentManager), + forceCreateEsConfigBasedRorSettings(resourcesPath), + (mockedIndexDocumentManager, expirationTimestamp) + ) + } + ) { case (rorInstance, (mockedIndexDocumentManager, expirationTimestamp)) => rorInstance.engines.value.impersonatorsEngine.value.core.accessControl shouldBe a[AccessControlListLoggingDecorator] - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Present( - config = RorConfig.disabled, - rawConfig = testConfig1, + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Present( + dependencies = RorDependencies.noOp, + rawSettings = testSettings1, configuredTtl = (100 seconds).toRefinedPositiveUnsafe, validTo = expirationTimestamp ) ) - (mockedIndexJsonContentManager.saveContent _) + (mockedIndexDocumentManager.saveDocumentJson _) .expects( where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(".readonlyrest") && + (index: IndexName.Full, id: String, document: Json) => + index == fullIndexName(".readonlyrest") && id == "2" && - content.get("settings").contains(testConfig1.raw) && - content.get("expiration_ttl_millis").contains("100000") && - content.get("expiration_timestamp").exists(_ != expirationTimestamp.toString) + document.hcursor.get[String]("settings").toOption.contains(testSettings1.rawYaml) && + document.hcursor.get[String]("expiration_ttl_millis").toOption.contains("100000") && + document.hcursor.get[String]("expiration_timestamp").toOption.exists(_ != expirationTimestamp.toString) } ) .repeated(1) .returns(Task.now(Left(CannotWriteToIndex))) - rorInstance.invalidateTestConfigEngine()(newRequestId()).runSyncUnsafe() should be( - Left(IndexConfigInvalidationError.IndexConfigSavingError(SavingIndexConfigError.CannotSaveConfig)) + rorInstance.invalidateTestSettingsEngine()(newRequestId()).runSyncUnsafe() should be( + Left(IndexSettingsInvalidationError.IndexSettingsSavingError(SettingsSavingError.SourceSpecificError(CannotSaveSettings))) ) rorInstance.engines.value.impersonatorsEngine should be(Option.empty) - rorInstance.currentTestConfig()(newRequestId()).runSyncUnsafe() should be( - TestConfig.Invalidated(testConfig1, (100 seconds).toRefinedPositiveUnsafe) + rorInstance.currentTestSettings()(newRequestId()).runSyncUnsafe() should be( + TestSettings.Invalidated(testSettings1, (100 seconds).toRefinedPositiveUnsafe) ) } } } "not be able to be loaded" when { "max size of ROR settings is exceeded" in { - val readonlyRest = readonlyRestBoot( - mock[CoreFactory], - mock[IndexJsonContentService], - "/boot_tests/forced_file_loading/", - maxYamlSize = Some("1 B") - ) + implicit val systemContext: SystemContext = createSystemContext() + val readonlyRest = readonlyRestBoot(mock[CoreFactory], mock[IndexDocumentManager]) - val result = readonlyRest.start().runSyncUnsafe() + val esConfigBasedRorSettings = { + val settings = forceCreateEsConfigBasedRorSettings("/boot_tests/forced_file_loading/") + settings.copy(settingsSource = settings.settingsSource.copy(settingsMaxSize = Bytes(1))) + } + + val result = readonlyRest.start(esConfigBasedRorSettings).runSyncUnsafe() inside(result) { case Left(StartingFailure(message, _)) => - message should include("Settings file is malformed") + message should include("ROR settings are malformed") message should include("The incoming YAML document exceeds the limit: 1 code points") } } "unable to setup data stream audit output" in { - val mockedIndexJsonContentManager = mock[IndexJsonContentService] - mockIndexJsonContentManagerSourceOfCallTestConfig(mockedIndexJsonContentManager) - val dataStreamSinkConfig1 = AuditSink.Config.EsDataStreamBasedSink.default val dataStreamSinkConfig2 = dataStreamSinkConfig1.copy( auditCluster = AuditCluster.RemoteAuditCluster(UniqueNonEmptyList.of(AuditClusterNode(Uri.parse("0.0.0.0"))), ClusterMode.RoundRobin, None) ) val coreFactory = mockCoreFactory( - mock[CoreFactory], + mockedCoreFactory = mock[CoreFactory], "/boot_tests/forced_file_loading_with_audit/readonlyrest.yml", mockEnabledAccessControl, - RorConfig(RorConfig.Services.empty, LocalUsers.empty, NoOpImpersonationWarningsReader, Some(AuditingTool.AuditSettings( + RorDependencies(RorDependencies.Services.empty, LocalUsers.empty, NoOpImpersonationWarningsReader), + Some(AuditingTool.AuditSettings( NonEmptyList.of( AuditSink.Enabled(dataStreamSinkConfig1), - AuditSink.Enabled(dataStreamSinkConfig2)) - ) + AuditSink.Enabled(dataStreamSinkConfig2) + ), + testEsNodeSettings )) ) @@ -1350,14 +1414,11 @@ class ReadonlyRestStartingTests .once() .returns(mockedDataStreamAuditSinkService(dataStreamService2)) - val readonlyRest = readonlyRestBoot( - coreFactory, - mockedIndexJsonContentManager, - "/boot_tests/forced_file_loading_with_audit/", - auditSinkServiceCreator - ) + implicit val systemContext: SystemContext = createSystemContext() + val readonlyRest = readonlyRestBoot(coreFactory, mock[IndexDocumentManager], auditSinkServiceCreator) + val esConfigBasedRorSettings = forceCreateEsConfigBasedRorSettings("/boot_tests/forced_file_loading_with_audit/") - val result = readonlyRest.start().runSyncUnsafe() + val result = readonlyRest.start(esConfigBasedRorSettings).runSyncUnsafe() inside(result) { case Left(StartingFailure(message, _)) => val expectedMessage = @@ -1370,38 +1431,25 @@ class ReadonlyRestStartingTests } } - private def withReadonlyRest(readonlyRest: ReadonlyRest)(testCode: RorInstance => Any): Unit = { - withReadonlyRestExt((readonlyRest, ())) { case (rorInstance, ()) => testCode(rorInstance) } + private def forceCreateEsConfigBasedRorSettings(resourceEsConfigDir: String) + (implicit systemContext: SystemContext) = { + createEsConfigBasedRorSettings(resourceEsConfigDir) match { + case Right(settings) => settings + case Left(error) => throw new IllegalStateException(s"Cannot create EsConfigBasedRorSettings: $error") + } } - private def withReadonlyRestExt[EXT](readonlyRestAndExt: (ReadonlyRest, EXT)) - (testCode: (RorInstance, EXT) => Any): Unit = { - val (readonlyRest, ext) = readonlyRestAndExt - Resource - .make( - acquire = readonlyRest - .start() - .flatMap { - case Right(startedInstance) => Task.now(startedInstance) - case Left(startingFailure) => Task.raiseError(new Exception(s"$startingFailure")) - } - )( - release = _.stop() - ) - .use { startedInstance => - Task.delay { - testCode(startedInstance, ext) - } - } + private def createEsConfigBasedRorSettings(resourceEsConfigDir: String) + (implicit systemContext: SystemContext): Either[LoadingError, EsConfigBasedRorSettings] = { + val esConfig = File(getResourcePath(resourceEsConfigDir)) + val esEnv = EsEnv(esConfig, esConfig, defaultEsVersionForTests, testEsNodeSettings) + EsConfigBasedRorSettings + .from(esEnv) .runSyncUnsafe() } - private def readonlyRestBoot(factory: CoreFactory, - indexJsonContentService: IndexJsonContentService, - configPath: String, - auditSinkServiceCreator: AuditSinkServiceCreator = mock[AuditSinkServiceCreator], - refreshInterval: Option[FiniteDuration] = None, - maxYamlSize: Option[String] = None): ReadonlyRest = { + private def createSystemContext(refreshInterval: Option[FiniteDuration] = None, + maxYamlSize: Option[String] = None): SystemContext = { def mapWithIntervalFrom(refreshInterval: Option[FiniteDuration]) = refreshInterval .map(i => "com.readonlyrest.settings.refresh.interval" -> i.toSeconds.toString) @@ -1412,8 +1460,8 @@ class ReadonlyRestStartingTests .map(size => "com.readonlyrest.settings.maxSize" -> size) .toMap - implicit val environmentConfig: EnvironmentConfig = new EnvironmentConfig( - propertiesProvider = TestsPropertiesProvider.usingMap( + new SystemContext(propertiesProvider = + TestsPropertiesProvider.usingMap( mapWithIntervalFrom(refreshInterval) ++ mapWithMaxYamlSize(maxYamlSize) ++ Map( @@ -1422,73 +1470,49 @@ class ReadonlyRestStartingTests ) ) ) - - ReadonlyRest.create( - factory, - indexJsonContentService, - auditSinkServiceCreator, - EsEnv(getResourcePath(configPath), getResourcePath(configPath), defaultEsVersionForTests, testEsNodeSettings), - ) - } - - private def mockIndexJsonContentManagerSourceOfCall(mockedManager: IndexJsonContentService, - resourceFileName: String, - repeatedCount: Int = 1) = { - (mockedManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "1") - .repeated(repeatedCount) - .returns(Task.now(Right( - Map("settings" -> getResourceContent(resourceFileName)) - ))) - mockedManager } - private def mockIndexJsonContentManagerSourceOfCallTestConfig(mockedManager: IndexJsonContentService) = { - (mockedManager.sourceOf _) - .expects(fullIndexName(".readonlyrest"), "2") - .anyNumberOfTimes() - .returns(Task.now(Left(ContentNotFound))) - mockedManager - } - - private def mockIndexJsonContentManagerSaveCall(mockedManager: IndexJsonContentService, - resourceFileName: String, - saveResult: Task[Either[WriteError, Unit]] = Task.now(Right(()))) = { - (mockedManager.saveContent _) - .expects(fullIndexName(".readonlyrest"), "1", Map("settings" -> getResourceContent(resourceFileName))) - .once() - .returns(saveResult) - mockedManager + private def readonlyRestBoot(factory: CoreFactory, + indexDocumentManager: IndexDocumentManager, + auditSinkServiceCreator: AuditSinkServiceCreator = mock[AuditSinkServiceCreator]) + (implicit systemContext: SystemContext): ReadonlyRest = { + ReadonlyRest.create(factory, indexDocumentManager, auditSinkServiceCreator) } private def mockCoreFactory(mockedCoreFactory: CoreFactory, - resourceFileName: String, + loadedMainSettingsResourceFileName: String, accessControlMock: AccessControlList = mockEnabledAccessControl, - rorConfig: RorConfig = RorConfig.disabled): CoreFactory = { - mockCoreFactory(mockedCoreFactory, rorConfigFromResource(resourceFileName), accessControlMock, rorConfig) + dependencies: RorDependencies = RorDependencies.noOp, + auditingSettings: Option[AuditingTool.AuditSettings] = None): CoreFactory = { + mockCoreFactory( + mockedCoreFactory, + rorSettingsFromResource(loadedMainSettingsResourceFileName), + accessControlMock, + dependencies, + auditingSettings + ) } private def mockCoreFactory(mockedCoreFactory: CoreFactory, - rawRorConfig: RawRorConfig, + loadedMainSettings: RawRorSettings, accessControlMock: AccessControlList, - rorConfig: RorConfig): CoreFactory = { + dependencies: RorDependencies, + auditingSettings: Option[AuditingTool.AuditSettings]): CoreFactory = { (mockedCoreFactory.createCoreFrom _) .expects(where { - (config: RawRorConfig, _, _, _, _) => config == rawRorConfig + (settings: RawRorSettings, _, _, _, _) => settings == loadedMainSettings }) .once() - .returns(Task.now(Right(Core(accessControlMock, rorConfig)))) + .returns(Task.now(Right(Core(accessControlMock, dependencies, auditingSettings)))) mockedCoreFactory } - private def disabledRorConfig = RorConfig.disabled - private def mockCoreFactory(mockedCoreFactory: CoreFactory, resourceFileName: String, - createCoreResult: Task[Either[NonEmptyList[CoreCreationError], Core]]) = { + createCoreResult: Task[Either[NonEmptyList[CoreCreationError], Core]]): CoreFactory = { (mockedCoreFactory.createCoreFrom _) .expects(where { - (config: RawRorConfig, _, _, _, _) => config == rorConfigFromResource(resourceFileName) + (settings: RawRorSettings, _, _, _, _) => settings == rorSettingsFromResource(resourceFileName) }) .once() .returns(createCoreResult) @@ -1497,14 +1521,14 @@ class ReadonlyRestStartingTests private def mockFailedCoreFactory(mockedCoreFactory: CoreFactory, resourceFileName: String): CoreFactory = { - mockFailedCoreFactory(mockedCoreFactory, rorConfigFromResource(resourceFileName)) + mockFailedCoreFactory(mockedCoreFactory, rorSettingsFromResource(resourceFileName)) } private def mockFailedCoreFactory(mockedCoreFactory: CoreFactory, - rawRorConfig: RawRorConfig): CoreFactory = { + rawRorSettings: RawRorSettings): CoreFactory = { (mockedCoreFactory.createCoreFrom _) .expects(where { - (config: RawRorConfig, _, _, _, _) => config == rawRorConfig + (settings: RawRorSettings, _, _, _, _) => settings == rawRorSettings }) .once() .returns(Task.now(Left(NonEmptyList.one(CoreCreationError.GeneralReadonlyrestSettingsError(Message("failed")))))) @@ -1551,7 +1575,7 @@ class ReadonlyRestStartingTests mockedContext } - private lazy val testConfig1 = rorConfigFromUnsafe( + private lazy val testSettings1 = rorSettingsFromUnsafe( """ |readonlyrest: | access_control_rules: @@ -1563,7 +1587,7 @@ class ReadonlyRestStartingTests |""".stripMargin ) - private lazy val testConfig2 = rorConfigFromUnsafe( + private lazy val testSettings2 = rorSettingsFromUnsafe( """ |readonlyrest: | access_control_rules: @@ -1575,7 +1599,7 @@ class ReadonlyRestStartingTests |""".stripMargin ) - private lazy val testConfigMalformed = rorConfigFromUnsafe( + private lazy val testSettingsMalformed = rorSettingsFromUnsafe( """ |readonlyrest: | access_control_rules: @@ -1641,7 +1665,6 @@ class ReadonlyRestStartingTests private def mockedDataStreamAuditSinkService(dataStreamService: DataStreamService) = { val dataStreamAuditSink = mock[DataStreamBasedAuditSinkService] - (() => dataStreamAuditSink.dataStreamCreator) .expects() .once() @@ -1649,6 +1672,92 @@ class ReadonlyRestStartingTests dataStreamAuditSink } + private def mockGettingMainSettingsReturnsError(mockedManager: IndexDocumentManager, + error: ReadError, + attemptCount: AttemptCount = AttemptCount.Exact(1)) = { + mockGettingSettings( + mockedManager = mockedManager, + expectedIndex = ".readonlyrest", + expectedDocument = "1", + returnsResponse = Task.now(Left(error)), + attemptCount = attemptCount + ) + } + + private def mockGettingMainSettings(mockedManager: IndexDocumentManager, + returnedMainSettingsResourceFileName: String, + attemptCount: AttemptCount = AttemptCount.Exact(1)) = { + mockGettingSettings( + mockedManager = mockedManager, + expectedIndex = ".readonlyrest", + expectedDocument = "1", + returnsResponse = Task.now(Right( + circeJsonFrom(s"""{ "settings": "${escapeJava(getResourceContent(returnedMainSettingsResourceFileName))}"}""") + )), + attemptCount = attemptCount + ) + } + + private def mockGettingTestSettings(mockedManager: IndexDocumentManager, + testSettingsJson: Json, + attemptCount: AttemptCount = AttemptCount.Exact(1)) = { + mockGettingSettings( + mockedManager = mockedManager, + expectedIndex = ".readonlyrest", + expectedDocument = "2", + returnsResponse = Task.now(Right(testSettingsJson)), + attemptCount = attemptCount + ) + } + + private def mockGettingTestSettingsReturnsError(mockedManager: IndexDocumentManager, + error: ReadError, + attemptCount: AttemptCount = AttemptCount.Exact(1)) = { + mockGettingSettings( + mockedManager = mockedManager, + expectedIndex = ".readonlyrest", + expectedDocument = "2", + returnsResponse = Task.now(Left(error)), + attemptCount = attemptCount + ) + } + + private def mockSavingMainSettings(mockedManager: IndexDocumentManager, + resourceFileName: String, + saveResult: Task[Either[WriteError, Unit]] = Task.now(Right(()))) = { + (mockedManager.saveDocumentJson _) + .expects( + fullIndexName(".readonlyrest"), + "1", + circeJsonFrom(s"""{ "settings": "${escapeJava(getResourceContent(resourceFileName))}"}""") + ) + .once() + .returns(saveResult) + mockedManager + } + + private def mockGettingSettings(mockedManager: IndexDocumentManager, + expectedIndex: String, + expectedDocument: String, + returnsResponse: Task[Either[ReadError, Json]], + attemptCount: AttemptCount) = { + val handler = (mockedManager.documentAsJson _) + .expects(fullIndexName(expectedIndex), expectedDocument) + val handlerWithRepetitions = attemptCount match { + case AttemptCount.AnyNumberOfTimes => handler.anyNumberOfTimes() + case AttemptCount.Exact(count) => handler.repeated(count) + } + handlerWithRepetitions + .returns(returnsResponse) + mockedManager + } + + private sealed trait AttemptCount + private object AttemptCount { + case object AnyNumberOfTimes extends AttemptCount + final case class Exact(count: Int) extends AttemptCount + } + // anonymous class instead of mock due to final defs and protected methods in DataStreamService private def mockedDataSteamService(dataStreamExists: Boolean, ilmExists: Boolean = false, diff --git a/core/src/test/scala/tech/beshu/ror/unit/boot/RorIndexTest.scala b/core/src/test/scala/tech/beshu/ror/unit/boot/RorIndexTest.scala deleted file mode 100644 index c13a86dd91..0000000000 --- a/core/src/test/scala/tech/beshu/ror/unit/boot/RorIndexTest.scala +++ /dev/null @@ -1,344 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.unit.boot - -import eu.timepit.refined.types.string.NonEmptyString -import monix.eval.Task -import monix.execution.Scheduler.Implicits.global -import org.scalamock.scalatest.MockFactory -import org.scalatest.concurrent.Eventually -import org.scalatest.matchers.should.Matchers.* -import org.scalatest.wordspec.AnyWordSpec -import org.scalatest.{EitherValues, Inside, OptionValues} -import tech.beshu.ror.accesscontrol.AccessControlList -import tech.beshu.ror.accesscontrol.AccessControlList.AccessControlStaticContext -import tech.beshu.ror.accesscontrol.audit.sink.AuditSinkServiceCreator -import tech.beshu.ror.accesscontrol.domain.{IndexName, RequestId} -import tech.beshu.ror.accesscontrol.factory.{Core, CoreFactory} -import tech.beshu.ror.boot.RorInstance.TestConfig -import tech.beshu.ror.boot.{ReadonlyRest, RorInstance} -import tech.beshu.ror.configuration.{EnvironmentConfig, RawRorConfig, RorConfig} -import tech.beshu.ror.es.{EsEnv, IndexJsonContentService} -import tech.beshu.ror.syntax.* -import tech.beshu.ror.utils.DurationOps.* -import tech.beshu.ror.utils.TestsPropertiesProvider -import tech.beshu.ror.utils.TestsUtils.* - -import java.util.UUID -import scala.concurrent.duration.* -import scala.language.postfixOps - -class RorIndexTest extends AnyWordSpec - with Inside with OptionValues with EitherValues - with MockFactory with Eventually { - - private val defaultRorIndexName: NonEmptyString = ".readonlyrest" - private val customRorIndexName: NonEmptyString = "custom_ror_index" - - private val mainRorConfigDocumentId = "1" - private val testRorConfigDocumentId = "2" - - "A ReadonlyREST core" should { - "load ROR index name" when { - "no index is defined in config" should { - "start ROR with default index name" in { - val resourcesPath = "/boot_tests/index_config/no_index_defined/" - val indexJsonContentService = mock[IndexJsonContentService] - mockMainRorConfigFromIndexLoading(indexJsonContentService, defaultRorIndexName) - mockTestRorConfigFromIndexLoading(indexJsonContentService, defaultRorIndexName) - - val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, indexRorConfig) - - val result = readonlyRestBoot( - coreFactory, - indexJsonContentService, - resourcesPath - ) - .start() - .runSyncUnsafe() - - result shouldBe a[Right[_, RorInstance]] - } - "save ROR config in default index" in { - val resourcesPath = "/boot_tests/index_config/no_index_defined/" - val indexJsonContentService = mock[IndexJsonContentService] - mockMainRorConfigFromIndexLoading(indexJsonContentService, defaultRorIndexName) - mockTestRorConfigFromIndexLoading(indexJsonContentService, defaultRorIndexName) - - val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, indexRorConfig) - - val result = readonlyRestBoot( - coreFactory, - indexJsonContentService, - resourcesPath - ) - .start() - .runSyncUnsafe() - - result shouldBe a[Right[_, RorInstance]] - - mockCoreFactory(coreFactory, rorConfig) - mockMainRorConfigInIndexSaving(indexJsonContentService, defaultRorIndexName) - - val rorInstance = result.value - val forceReloadingResult = - rorInstance - .forceReloadAndSave(rorConfig)(newRequestId()) - .runSyncUnsafe() - - forceReloadingResult should be(Right(())) - } - "save ROR test config in default index" in { - val resourcesPath = "/boot_tests/index_config/no_index_defined/" - val indexJsonContentService = mock[IndexJsonContentService] - mockMainRorConfigFromIndexLoading(indexJsonContentService, defaultRorIndexName) - mockTestRorConfigFromIndexLoading(indexJsonContentService, defaultRorIndexName) - - val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, indexRorConfig) - - val result = readonlyRestBoot( - coreFactory, - indexJsonContentService, - resourcesPath - ) - .start() - .runSyncUnsafe() - - result shouldBe a[Right[_, RorInstance]] - - mockCoreFactory(coreFactory, rorConfig) - mockTestRorConfigInIndexSaving(indexJsonContentService, defaultRorIndexName) - - val rorInstance = result.value - val forceReloadingResult = - rorInstance - .forceReloadTestConfigEngine(rorConfig, (5 minutes).toRefinedPositiveUnsafe)(newRequestId()) - .runSyncUnsafe() - - forceReloadingResult.value shouldBe a[TestConfig.Present] - } - } - "custom index is defined in config" should { - "start ROR with custom index name" in { - val resourcesPath = "/boot_tests/index_config/custom_index_defined/" - val indexJsonContentService = mock[IndexJsonContentService] - mockMainRorConfigFromIndexLoading(indexJsonContentService, customRorIndexName) - mockTestRorConfigFromIndexLoading(indexJsonContentService, customRorIndexName) - - val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, indexRorConfig) - - val result = readonlyRestBoot( - coreFactory, - indexJsonContentService, - resourcesPath - ) - .start() - .runSyncUnsafe() - result shouldBe a[Right[_, RorInstance]] - } - "save ROR config in custom index" in { - val resourcesPath = "/boot_tests/index_config/custom_index_defined/" - val indexJsonContentService = mock[IndexJsonContentService] - mockMainRorConfigFromIndexLoading(indexJsonContentService, customRorIndexName) - mockTestRorConfigFromIndexLoading(indexJsonContentService, customRorIndexName) - - val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, indexRorConfig) - - val result = readonlyRestBoot( - coreFactory, - indexJsonContentService, - resourcesPath - ) - .start() - .runSyncUnsafe() - - mockCoreFactory(coreFactory, rorConfig) - mockMainRorConfigInIndexSaving(indexJsonContentService, customRorIndexName) - - val rorInstance = result.value - val forceReloadingResult = - rorInstance - .forceReloadAndSave(rorConfig)(newRequestId()) - .runSyncUnsafe() - - forceReloadingResult should be(Right(())) - } - "save ROR test config in custom index" in { - val resourcesPath = "/boot_tests/index_config/custom_index_defined/" - val indexJsonContentService = mock[IndexJsonContentService] - mockMainRorConfigFromIndexLoading(indexJsonContentService, customRorIndexName) - mockTestRorConfigFromIndexLoading(indexJsonContentService, customRorIndexName) - - val coreFactory = mock[CoreFactory] - mockCoreFactory(coreFactory, indexRorConfig) - - val result = readonlyRestBoot( - coreFactory, - indexJsonContentService, - resourcesPath - ) - .start() - .runSyncUnsafe() - - mockCoreFactory(coreFactory, rorConfig) - mockTestRorConfigInIndexSaving(indexJsonContentService, customRorIndexName) - - val rorInstance = result.value - val forceReloadingResult = - rorInstance - .forceReloadTestConfigEngine(rorConfig, (5 minutes).toRefinedPositiveUnsafe)(newRequestId()) - .runSyncUnsafe() - - forceReloadingResult.value shouldBe a[TestConfig.Present] - } - } - } - } - - private def readonlyRestBoot(factory: CoreFactory, - indexJsonContentService: IndexJsonContentService, - configPath: String) = { - implicit val environmentConfig: EnvironmentConfig = new EnvironmentConfig( - propertiesProvider = TestsPropertiesProvider.usingMap( - Map( - "com.readonlyrest.settings.loading.delay" -> "1" - ) - ) - ) - - ReadonlyRest.create( - factory, - indexJsonContentService, - mock[AuditSinkServiceCreator], - EsEnv(getResourcePath(configPath), getResourcePath(configPath), defaultEsVersionForTests, testEsNodeSettings), - ) - } - - private def mockCoreFactory(mockedCoreFactory: CoreFactory, - rawRorConfig: RawRorConfig): CoreFactory = { - (mockedCoreFactory.createCoreFrom _) - .expects(where { - (config: RawRorConfig, _, _, _, _) => config == rawRorConfig - }) - .once() - .returns(Task.now(Right(Core(mockAccessControl, RorConfig.disabled)))) - mockedCoreFactory - } - - private def mockMainRorConfigFromIndexLoading(indexJsonContentService: IndexJsonContentService, - indexName: NonEmptyString) = { - (indexJsonContentService.sourceOf _) - .expects(fullIndexName(indexName), mainRorConfigDocumentId) - .once() - .returns(Task.now(Right(Map("settings" -> indexRorConfig.raw)))) - } - - private def mockTestRorConfigFromIndexLoading(indexJsonContentService: IndexJsonContentService, - indexName: NonEmptyString) = { - (indexJsonContentService.sourceOf _) - .expects(fullIndexName(indexName), testRorConfigDocumentId) - .once() - .returns(Task.now(Left(IndexJsonContentService.ContentNotFound))) - } - - private def mockMainRorConfigInIndexSaving(indexJsonContentService: IndexJsonContentService, - indexName: NonEmptyString) = { - (indexJsonContentService.saveContent _) - .expects(fullIndexName(indexName), mainRorConfigDocumentId, Map("settings" -> rorConfig.raw)) - .once() - .returns(Task.now(Right(()))) - } - - private def mockTestRorConfigInIndexSaving(indexJsonContentService: IndexJsonContentService, - indexName: NonEmptyString) = { - (indexJsonContentService.saveContent _) - .expects( - where { - (config: IndexName.Full, id: String, content: Map[String, String]) => - config == fullIndexName(indexName) && - id == testRorConfigDocumentId && - content.get("settings").contains(rorConfig.raw) && - content.get("expiration_ttl_millis").contains("300000") && - content.contains("expiration_timestamp") && - content.contains("auth_services_mocks") - } - ) - .once() - .returns(Task.now(Right(()))) - } - - private def mockAccessControl = { - val mockedAccessControl = mock[AccessControlList] - (() => mockedAccessControl.staticContext) - .expects() - .anyNumberOfTimes() - .returns(mockAccessControlStaticContext) - (() => mockedAccessControl.description) - .expects() - .anyNumberOfTimes() - .returns("ENABLED") - mockedAccessControl - } - - private def mockAccessControlStaticContext = { - val mockedContext = mock[AccessControlStaticContext] - (() => mockedContext.obfuscatedHeaders) - .expects() - .anyNumberOfTimes() - .returns(Set.empty) - - (() => mockedContext.usedFlsEngineInFieldsRule) - .expects() - .anyNumberOfTimes() - .returns(None) - mockedContext - } - - private def newRequestId() = RequestId(UUID.randomUUID().toString) - - private lazy val rorConfig = rorConfigFromUnsafe( - """ - |readonlyrest: - | access_control_rules: - | - | - name: test_block - | type: allow - | auth_key: admin:container - | - | - name: test_block_2 - | type: allow - | auth_key: dev:container - | - |""".stripMargin - ) - - private lazy val indexRorConfig = rorConfigFromUnsafe( - """ - |readonlyrest: - | access_control_rules: - | - name: test_block - | type: allow - | auth_key: admin:container - |""".stripMargin - ) - -} diff --git a/core/src/test/scala/tech/beshu/ror/unit/configuration/LoadRawRorConfigTest.scala b/core/src/test/scala/tech/beshu/ror/unit/configuration/LoadRawRorConfigTest.scala deleted file mode 100644 index 4389c367a2..0000000000 --- a/core/src/test/scala/tech/beshu/ror/unit/configuration/LoadRawRorConfigTest.scala +++ /dev/null @@ -1,162 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.unit.configuration - -import cats.{Id, ~>} -import io.circe.Json -import org.scalatest.EitherValues -import org.scalatest.matchers.should.Matchers.* -import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.accesscontrol.domain.{IndexName, RorConfigurationIndex} -import tech.beshu.ror.configuration.ConfigLoading.LoadConfigAction -import tech.beshu.ror.configuration.ConfigLoading.LoadConfigAction.* -import tech.beshu.ror.configuration.RawRorConfig -import tech.beshu.ror.configuration.RorProperties.{LoadingAttemptsCount, LoadingAttemptsInterval, LoadingDelay} -import tech.beshu.ror.configuration.loader.LoadedRorConfig.{FileConfig, ForcedFileConfig, IndexConfig} -import tech.beshu.ror.configuration.loader.{LoadRawRorConfig, LoadedRorConfig} -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.utils.TestsUtils.{defaultEsVersionForTests, testEsNodeSettings, unsafeNes} - -import java.nio.file.Paths -import scala.concurrent.duration.* -import scala.language.{existentials, postfixOps} - -class LoadRawRorConfigTest extends AnyWordSpec with EitherValues{ - import LoadRawRorConfigTest.* - "Free monad loader program" should { - "load forced file" in { - val steps = List( - (ForceLoadRorConfigFromFile(esEnv.configPath), Right(ForcedFileConfig(rawRorConfig))), - ) - val compiler = IdCompiler.instance(steps) - val program = LoadRawRorConfig.loadFromFile(esEnv.configPath) - val result = program.foldMap(compiler) - val ffc = result.asInstanceOf[Right[Nothing, ForcedFileConfig[RawRorConfig]]] - ffc.value.value shouldEqual rawRorConfig - } - "load successfully from index" in { - val loadingDelay = LoadingDelay.unsafeFrom(2 seconds) - val steps = List( - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingDelay.value), Right(IndexConfig(rorConfigurationIndex, rawRorConfig))), - ) - val compiler = IdCompiler.instance(steps) - val program = LoadRawRorConfig.loadFromIndexWithFileFallback( - configurationIndex = rorConfigurationIndex, - loadingDelay = loadingDelay, - loadingAttemptsCount = LoadingAttemptsCount.unsafeFrom(1), - loadingAttemptsInterval = LoadingAttemptsInterval.unsafeFrom(1 second), - fallbackConfigFilePath = esEnv.configPath - ) - val result = program.foldMap(compiler) - val ffc = result.asInstanceOf[Right[Nothing, IndexConfig[RawRorConfig]]] - ffc.value.value shouldEqual rawRorConfig - } - "load successfully from index, after failure" in { - val loadingDelay = LoadingDelay.unsafeFrom(2 seconds) - val loadingAttemptsInterval = LoadingAttemptsInterval.unsafeFrom(1 second) - val steps = List( - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingDelay.value), Left(LoadedRorConfig.IndexNotExist)), - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingAttemptsInterval.value), Right(IndexConfig(rorConfigurationIndex, rawRorConfig))), - ) - val compiler = IdCompiler.instance(steps) - val program = LoadRawRorConfig.loadFromIndexWithFileFallback( - configurationIndex = rorConfigurationIndex, - loadingDelay = loadingDelay, - loadingAttemptsCount = LoadingAttemptsCount.unsafeFrom(5), - loadingAttemptsInterval = loadingAttemptsInterval, - fallbackConfigFilePath = esEnv.configPath - ) - val result = program.foldMap(compiler) - val ffc = result.asInstanceOf[Right[Nothing, IndexConfig[RawRorConfig]]] - ffc.value.value shouldEqual rawRorConfig - } - "load from file when index not exist" in { - val loadingDelay = LoadingDelay.unsafeFrom(2 seconds) - val loadingAttemptsInterval = LoadingAttemptsInterval.unsafeFrom(1 second) - val steps = List( - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingDelay.value), Left(LoadedRorConfig.IndexNotExist)), - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingAttemptsInterval.value), Left(LoadedRorConfig.IndexNotExist)), - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingAttemptsInterval.value), Left(LoadedRorConfig.IndexNotExist)), - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingAttemptsInterval.value), Left(LoadedRorConfig.IndexNotExist)), - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingAttemptsInterval.value), Left(LoadedRorConfig.IndexNotExist)), - (LoadRorConfigFromFile(esEnv.configPath), Right(FileConfig(rawRorConfig))), - ) - val compiler = IdCompiler.instance(steps) - val program = LoadRawRorConfig.loadFromIndexWithFileFallback( - configurationIndex = rorConfigurationIndex, - loadingDelay = loadingDelay, - loadingAttemptsCount = LoadingAttemptsCount.unsafeFrom(5), - loadingAttemptsInterval = loadingAttemptsInterval, - fallbackConfigFilePath = esEnv.configPath - ) - val result = program.foldMap(compiler) - result.toOption.get shouldBe FileConfig(rawRorConfig) - } - "unknown index structure fail loading from index immediately" in { - val loadingDelay = LoadingDelay.unsafeFrom(2 seconds) - val steps = List( - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingDelay.value), Left(LoadedRorConfig.IndexUnknownStructure)), - ) - val compiler = IdCompiler.instance(steps) - val program = LoadRawRorConfig.loadFromIndexWithFileFallback( - configurationIndex = rorConfigurationIndex, - loadingDelay = loadingDelay, - loadingAttemptsCount = LoadingAttemptsCount.unsafeFrom(5), - loadingAttemptsInterval = LoadingAttemptsInterval.unsafeFrom(1 second), - fallbackConfigFilePath = esEnv.configPath - ) - val result = program.foldMap(compiler) - result shouldBe a[Left[LoadedRorConfig.IndexUnknownStructure.type, _]] - } - "parse index error fail loading from index immediately" in { - val loadingDelay = LoadingDelay.unsafeFrom(2 seconds) - val steps = List( - (LoadRorConfigFromIndex(rorConfigurationIndex, loadingDelay.value), Left(LoadedRorConfig.IndexParsingError("error"))), - ) - val compiler = IdCompiler.instance(steps) - val program = LoadRawRorConfig.loadFromIndexWithFileFallback( - configurationIndex = rorConfigurationIndex, - loadingDelay = loadingDelay, - loadingAttemptsCount = LoadingAttemptsCount.unsafeFrom(5), - loadingAttemptsInterval = LoadingAttemptsInterval.unsafeFrom(1 second), - fallbackConfigFilePath = esEnv.configPath - ) - val result = program.foldMap(compiler) - result shouldBe a[Left[LoadedRorConfig.IndexParsingError, _]] - result.left.value.asInstanceOf[LoadedRorConfig.IndexParsingError].message shouldBe "error" - } - } -} -object LoadRawRorConfigTest { - - private val esEnv = EsEnv(Paths.get("unused_file_path"), Paths.get("unused_file_path"), defaultEsVersionForTests, testEsNodeSettings) - private val rawRorConfig = RawRorConfig(Json.False, "forced file config") - private val rorConfigurationIndex = RorConfigurationIndex(IndexName.Full("rorConfigurationIndex")) - -} -object IdCompiler { - def instance(mocksDef: List[(LoadConfigAction[_], _)]): LoadConfigAction ~> Id = new (LoadConfigAction ~> Id) { - var mocks: List[(LoadConfigAction[_], _)] = mocksDef - - override def apply[A](fa: LoadConfigAction[A]): Id[A] = { - val (f1, r) = mocks.head - mocks = mocks.tail - assert(f1 == fa) - r.asInstanceOf[Id[A]] - } - } -} \ No newline at end of file diff --git a/core/src/test/scala/tech/beshu/ror/unit/configuration/RorBootConfigurationTest.scala b/core/src/test/scala/tech/beshu/ror/unit/configuration/RorBootConfigurationTest.scala deleted file mode 100644 index 749096f651..0000000000 --- a/core/src/test/scala/tech/beshu/ror/unit/configuration/RorBootConfigurationTest.scala +++ /dev/null @@ -1,103 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.unit.configuration - -import monix.execution.Scheduler.Implicits.global -import org.scalatest.Inside -import org.scalatest.matchers.should.Matchers.* -import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} -import tech.beshu.ror.configuration.{EnvironmentConfig, MalformedSettings, RorBootConfiguration} -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.utils.TestsUtils.{defaultEsVersionForTests, getResourcePath, testEsNodeSettings} - -class RorBootConfigurationTest - extends AnyWordSpec with Inside { - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - "A ReadonlyREST ES starting settings" should { - "be loaded from elasticsearch config file" when { - "configuration contains not started response code" in { - val config = RorBootConfiguration - .load(esEnvFrom("/boot_tests/boot_config/not_started_code_defined/")) - .runSyncUnsafe() - - config.map(_.rorNotStartedResponse) should be(Right( - RorNotStartedResponse(RorNotStartedResponse.HttpCode.`503`) - )) - } - "configuration contains failed to start response code" in { - val config = RorBootConfiguration - .load(esEnvFrom("/boot_tests/boot_config/failed_to_start_code_defined/")) - .runSyncUnsafe() - - config.map(_.rorFailedToStartResponse) should be(Right( - RorFailedToStartResponse(RorFailedToStartResponse.HttpCode.`503`) - )) - } - "configuration contains all codes" in { - val config = RorBootConfiguration - .load(esEnvFrom("/boot_tests/boot_config/all_codes_defined/")) - .runSyncUnsafe() - - config should be(Right(RorBootConfiguration( - rorNotStartedResponse = RorNotStartedResponse(RorNotStartedResponse.HttpCode.`403`), - rorFailedToStartResponse = RorFailedToStartResponse(RorFailedToStartResponse.HttpCode.`503`), - ))) - } - } - "there is no response codes defined in config, default values should be used" in { - val config = RorBootConfiguration - .load(esEnvFrom("/boot_tests/boot_config/")) - .runSyncUnsafe() - - config should be(Right(RorBootConfiguration( - rorNotStartedResponse = RorNotStartedResponse(RorNotStartedResponse.HttpCode.`403`), - rorFailedToStartResponse = RorFailedToStartResponse(RorFailedToStartResponse.HttpCode.`403`), - ))) - } - } - "not be able to load" when { - "not started response code is malformed" in { - val configFolderPath = "/boot_tests/boot_config/not_started_code_malformed/" - val expectedFilePath = getResourcePath(s"${configFolderPath}elasticsearch.yml").toString - - RorBootConfiguration.load(esEnvFrom(configFolderPath)).runSyncUnsafe() shouldBe Left { - MalformedSettings( - s"Cannot load ROR boot configuration from file $expectedFilePath. " + - s"Cause: Unsupported response code [200] for readonlyrest.not_started_response_code. Supported response codes are: 403, 503." - ) - } - } - "failed to start response code is malformed" in { - val configFolderPath = "/boot_tests/boot_config/failed_to_start_code_malformed/" - val expectedFilePath = getResourcePath(s"${configFolderPath}elasticsearch.yml").toString - - RorBootConfiguration.load(esEnvFrom(configFolderPath)).runSyncUnsafe() shouldBe Left { - MalformedSettings( - s"Cannot load ROR boot configuration from file $expectedFilePath. " + - s"Cause: Unsupported response code [200] for readonlyrest.failed_to_start_response_code. Supported response codes are: 403, 503." - ) - } - } - } - - private def esEnvFrom(configFolderPath: String) = { - EsEnv(getResourcePath(configFolderPath), getResourcePath(configFolderPath), defaultEsVersionForTests, testEsNodeSettings) - } -} diff --git a/core/src/test/scala/tech/beshu/ror/unit/configuration/SslConfigurationTest.scala b/core/src/test/scala/tech/beshu/ror/unit/configuration/SslConfigurationTest.scala deleted file mode 100644 index 2c51b924a1..0000000000 --- a/core/src/test/scala/tech/beshu/ror/unit/configuration/SslConfigurationTest.scala +++ /dev/null @@ -1,220 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.unit.configuration - -import monix.execution.Scheduler.Implicits.global -import org.scalatest.Inside -import org.scalatest.matchers.should.Matchers.* -import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.configuration.SslConfiguration.* -import tech.beshu.ror.configuration.SslConfiguration.ServerCertificateConfiguration.{FileBasedConfiguration, KeystoreBasedConfiguration} -import tech.beshu.ror.configuration.{EnvironmentConfig, MalformedSettings, RorSsl} -import tech.beshu.ror.es.EsEnv -import tech.beshu.ror.utils.TestsPropertiesProvider -import tech.beshu.ror.utils.TestsUtils.{defaultEsVersionForTests, getResourcePath, testEsNodeSettings} - -class SslConfigurationTest - extends AnyWordSpec with Inside { - - private implicit val environmentConfig: EnvironmentConfig = new EnvironmentConfig( - propertiesProvider = TestsPropertiesProvider.default - ) - - "A ReadonlyREST ES API SSL settings" should { - "be loaded from elasticsearch config file" when { - "all properties contain at least one non-digit" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/es_api_ssl_settings_in_elasticsearch_config/")).runSyncUnsafe().toOption.get - inside(ssl.externalSsl) { - case Some(ExternalSslConfiguration(KeystoreBasedConfiguration(keystoreFile, Some(keystorePassword), None, Some(keyPass)), Some(ClientCertificateConfiguration.TruststoreBasedConfiguration(truststoreFile, Some(truststorePassword))), allowedProtocols, allowedCiphers, clientAuthenticationEnabled)) => - keystoreFile.value.getName should be("ror-keystore.jks") - keystorePassword should be(KeystorePassword("readonlyrest1")) - keyPass should be(KeyPass("readonlyrest2")) - truststoreFile.value.getName should be("ror-truststore.jks") - truststorePassword should be(TruststorePassword("readonlyrest3")) - allowedProtocols should be(Set.empty) - allowedCiphers should be(Set.empty) - clientAuthenticationEnabled should be(false) - } - ssl.interNodeSsl should be(None) - } - "some properties contains only digits" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/es_api_ssl_settings_in_elasticsearch_config_only_digits/")).runSyncUnsafe().toOption.get - inside(ssl.externalSsl) { - case Some(ExternalSslConfiguration(KeystoreBasedConfiguration(keystoreFile, Some(keystorePassword), None, Some(keyPass)), Some(ClientCertificateConfiguration.TruststoreBasedConfiguration(truststoreFile, Some(truststorePassword))), allowedProtocols, allowedCiphers, clientAuthenticationEnabled)) => - keystoreFile.value.getName should be("ror-keystore.jks") - keystorePassword should be(KeystorePassword("123456")) - keyPass should be(KeyPass("12")) - truststoreFile.value.getName should be("ror-truststore.jks") - truststorePassword should be(TruststorePassword("1234")) - allowedProtocols should be(Set.empty) - allowedCiphers should be(Set.empty) - clientAuthenticationEnabled should be(false) - } - ssl.interNodeSsl should be(None) - } - "server and client are configured using pem files" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/es_api_ssl_settings_pem_files/")).runSyncUnsafe().toOption.get - inside(ssl.externalSsl) { - case Some(ExternalSslConfiguration(FileBasedConfiguration(serverCertificateKeyFile, serverCertificateFile), Some(ClientCertificateConfiguration.FileBasedConfiguration(clientTrustedCertificateFile)), allowedProtocols, allowedCiphers, clientAuthenticationEnabled)) => - serverCertificateKeyFile.value.getName should be("server_certificate_key.pem") - serverCertificateFile.value.getName should be("server_certificate.pem") - clientTrustedCertificateFile.value.getName should be("client_certificate.pem") - allowedProtocols should be(Set.empty) - allowedCiphers should be(Set.empty) - clientAuthenticationEnabled should be(false) - } - } - } - "be loaded from readonlyrest config file" when { - "elasticsearch config file doesn't contain ROR ssl section" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/es_api_ssl_settings_in_readonlyrest_config/")).runSyncUnsafe().toOption.get - inside(ssl.externalSsl) { - case Some(ExternalSslConfiguration(KeystoreBasedConfiguration(keystoreFile, Some(keystorePassword), None, Some(keyPass)), Some(ClientCertificateConfiguration.TruststoreBasedConfiguration(truststoreFile, Some(truststorePassword))), allowedProtocols, allowedCiphers, clientAuthenticationEnabled)) => - keystoreFile.value.getName should be("ror-keystore.jks") - keystorePassword should be(KeystorePassword("readonlyrest1")) - keyPass should be(KeyPass("readonlyrest2")) - truststoreFile.value.getName should be("ror-truststore.jks") - truststorePassword should be(TruststorePassword("readonlyrest3")) - allowedProtocols should be(Set.empty) - allowedCiphers should be(Set.empty) - clientAuthenticationEnabled should be(false) - } - ssl.interNodeSsl should be(None) - } - } - "be disabled" when { - "no ssl section is provided" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/no_es_api_ssl_settings/")).runSyncUnsafe().toOption.get - ssl.externalSsl should be(None) - ssl.interNodeSsl should be(None) - } - "it's disabled by proper settings" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/es_api_ssl_settings_disabled/")).runSyncUnsafe().toOption.get - ssl.externalSsl should be(None) - ssl.interNodeSsl should be(None) - } - } - "not be able to load" when { - "SSL settings are malformed" when { - "keystore_file entry is missing" in { - val configFolderPath = "/boot_tests/es_api_ssl_settings_malformed/" - val expectedFilePath = getResourcePath(s"${configFolderPath}elasticsearch.yml").toString - RorSsl.load(esEnvFrom(configFolderPath)).runSyncUnsafe() shouldBe Left { - MalformedSettings(s"Cannot load ROR SSL configuration from file $expectedFilePath. Cause: Missing required field") - } - } - } - "file content is not valid yaml" in { - val error = RorSsl.load(esEnvFrom("/boot_tests/es_api_ssl_settings_file_invalid_yaml/")).runSyncUnsafe() - inside(error) { - case Left(error) => - error.message should startWith("Cannot parse file") - - } - } - "SSL settings contain both pem and truststore based configuration" in { - val configFolderPath = "/boot_tests/es_api_ssl_settings_both_pem_and_keystore_configured/" - val expectedFilePath = getResourcePath(s"${configFolderPath}elasticsearch.yml").toString - - RorSsl.load(esEnvFrom(configFolderPath)).runSyncUnsafe() shouldBe Left { - MalformedSettings( - s"Cannot load ROR SSL configuration from file $expectedFilePath. " + - s"Cause: Field sets [server_certificate_key_file, server_certificate_file] and [keystore_file, keystore_pass, key_pass, key_alias] could not be present in the same configuration section") - } - } - } - } - - "A ReadonlyREST internode SSL settings" should { - "be loaded from elasticsearch config file" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/internode_ssl_settings_in_elasticsearch_config/")).runSyncUnsafe().toOption.get - inside(ssl.interNodeSsl) { - case Some(InternodeSslConfiguration(KeystoreBasedConfiguration(keystoreFile, Some(keystorePassword), None, Some(keyPass)), truststoreConfiguration, allowedProtocols, allowedCiphers, clientAuthenticationEnabled, certificateVerificationEnabled, hostnameVerificationEnabled)) => - keystoreFile.value.getName should be("ror-keystore.jks") - keystorePassword should be(KeystorePassword("readonlyrest1")) - keyPass should be(KeyPass("readonlyrest2")) - truststoreConfiguration should be(None) - allowedProtocols should be(Set.empty) - allowedCiphers should be(Set.empty) - clientAuthenticationEnabled should be(false) - certificateVerificationEnabled should be(true) - hostnameVerificationEnabled should be (true) - } - ssl.externalSsl should be(None) - } - "be loaded from elasticsearch config file when pem files are used" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/internode_ssl_settings_pem_files/")).runSyncUnsafe().toOption.get - inside(ssl.interNodeSsl) { - case Some(InternodeSslConfiguration(FileBasedConfiguration(serverCertificateKeyFile, serverCertificateFile), Some(ClientCertificateConfiguration.FileBasedConfiguration(clientTrustedCertificateFile)), allowedProtocols, allowedCiphers, clientAuthenticationEnabled, certificateVerificationEnabled, hostnameVerificationEnabled)) => - serverCertificateKeyFile.value.getName should be("server_certificate_key.pem") - serverCertificateFile.value.getName should be("server_certificate.pem") - clientTrustedCertificateFile.value.getName should be("client_certificate.pem") - allowedProtocols should be(Set.empty) - allowedCiphers should be(Set.empty) - clientAuthenticationEnabled should be(false) - certificateVerificationEnabled should be(true) - hostnameVerificationEnabled should be (false) - } - } - "be loaded from readonlyrest config file" when { - "elasticsearch config file doesn't contain ROR ssl section" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/internode_ssl_settings_in_readonlyrest_config/")).runSyncUnsafe().toOption.get - inside(ssl.interNodeSsl) { - case Some(InternodeSslConfiguration(KeystoreBasedConfiguration(keystoreFile, Some(keystorePassword), None, Some(keyPass)), Some(ClientCertificateConfiguration.TruststoreBasedConfiguration(truststoreFile, Some(truststorePassword))), allowedProtocols, allowedCiphers, clientAuthenticationEnabled, certificateVerificationEnabled, hostnameVerificationEnabled)) => - keystoreFile.value.getName should be("ror-keystore.jks") - keystorePassword should be(KeystorePassword("readonlyrest1")) - keyPass should be(KeyPass("readonlyrest2")) - truststoreFile.value.getName should be("ror-truststore.jks") - truststorePassword should be(TruststorePassword("readonlyrest3")) - allowedProtocols should be(Set.empty) - allowedCiphers should be(Set.empty) - clientAuthenticationEnabled should be(false) - certificateVerificationEnabled should be(true) - hostnameVerificationEnabled should be (true) - } - ssl.externalSsl should be(None) - } - } - "be disabled" when { - "no ssl section is provided" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/no_internode_ssl_settings/")).runSyncUnsafe().toOption.get - ssl.externalSsl should be(None) - ssl.interNodeSsl should be(None) - } - "it's disabled by proper settings" in { - val ssl = RorSsl.load(esEnvFrom("/boot_tests/internode_ssl_settings_disabled/")).runSyncUnsafe().toOption.get - ssl.externalSsl should be(None) - ssl.interNodeSsl should be(None) - } - } - "not be able to load" when { - "SSL settings are malformed" when { - "keystore_file entry is missing" in { - val configFolderPath = "/boot_tests/internode_ssl_settings_malformed/" - val expectedFilePath = getResourcePath(s"${configFolderPath}elasticsearch.yml").toString - RorSsl.load(esEnvFrom(configFolderPath)).runSyncUnsafe() shouldBe Left { - MalformedSettings(s"Cannot load ROR SSL configuration from file $expectedFilePath. Cause: Missing required field") - } - } - } - } - } - - private def esEnvFrom(configFolderPath: String) = { - EsEnv(getResourcePath(configFolderPath), getResourcePath(configFolderPath), defaultEsVersionForTests, testEsNodeSettings) - } -} diff --git a/core/src/test/scala/tech/beshu/ror/unit/configuration/YamlFileBasedConfigLoaderTest.scala b/core/src/test/scala/tech/beshu/ror/unit/configuration/YamlFileBasedConfigLoaderTest.scala deleted file mode 100644 index 4c827606e3..0000000000 --- a/core/src/test/scala/tech/beshu/ror/unit/configuration/YamlFileBasedConfigLoaderTest.scala +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.unit.configuration - -import better.files.File -import cats.implicits.* -import io.circe.Decoder -import org.scalatest.Inside -import org.scalatest.matchers.should.Matchers.* -import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.configuration.{EnvironmentConfig, YamlFileBasedConfigLoader} - -class YamlFileBasedConfigLoaderTest extends AnyWordSpec with Inside { - - private implicit val environmentConfig: EnvironmentConfig = new EnvironmentConfig( - envVarsProvider = name => - name.value.value match { - case "USER_NAME" => Some("John") - case _ => None - } - ) - - "YamlFileBasedConfigLoader" should { - "decode file file" in { - val result = loadFromTempFile[String](""""encoded value"""") - result shouldBe "encoded value".asRight - } - "decode file with variable" in { - val result = loadFromTempFile[String](""""${USER_NAME}"""") - result shouldBe "John".asRight - } - "decode file with variable with transformation" in { - val result = loadFromTempFile[String](""""${USER_NAME}#{to_uppercase}"""") - result shouldBe "JOHN".asRight - } - "fail for non existing vairable" in { - val result = loadFromTempFile[String](""""${WRONG_VARIABLE}"""") - inside(result) { - case Left(error) => - error.message should include("WRONG_VARIABLE") - } - } - } - - private def loadFromTempFile[A: Decoder](content: String) = - tempFile(content).map { file => - createFileConfigLoader(file) - .loadConfig[A]("TEST") - }.get() - - private def tempFile(content: String) = File.temporaryFile().map(_.write(content)) - - private def createFileConfigLoader(file: File) = new YamlFileBasedConfigLoader(file) -} diff --git a/core/src/test/scala/tech/beshu/ror/unit/configuration/loader/ResultDTOTest.scala b/core/src/test/scala/tech/beshu/ror/unit/configuration/loader/ResultDTOTest.scala deleted file mode 100644 index 612457a224..0000000000 --- a/core/src/test/scala/tech/beshu/ror/unit/configuration/loader/ResultDTOTest.scala +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.unit.configuration.loader - -import cats.implicits.* -import io.circe.syntax.* -import org.scalatest.matchers.should.Matchers.* -import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.configuration.loader.distributed.{NodesResponse, Summary} -import tech.beshu.ror.configuration.loader.external.dto.{LoadedConfigDTO, NodesResponseWaringDTO, ResultDTO} - -import scala.language.postfixOps - -class ResultDTOTest extends AnyWordSpec { - "ResultDTO" when { - "result failed" should { - "return current node " in { - val expectedResult = ResultDTO(None, Nil, "current node returned error: index unknown structure" some) - ResultDTO.create(Summary.CurrentNodeConfigError(LoadedRorConfig.IndexUnknownStructure) asLeft) shouldEqual expectedResult - } - "return current node failure" in { - val expectedResult = ResultDTO(None, Nil, "current node response error: null pointer" some) - ResultDTO.create(Summary.CurrentNodeResponseError("null pointer") asLeft) shouldEqual expectedResult - } - } - "return result" should { - val warnings = Summary.NodeReturnedConfigError(NodesResponse.NodeId("n2"), LoadedRorConfig.IndexUnknownStructure) :: - Summary.NodeForcedFileConfig(NodesResponse.NodeId("n1")) :: - Nil - val result = ResultDTO.create(Summary.Result(LoadedRorConfig.ForcedFileConfig("config"), warnings) asRight) - "be as DTO" in { - val expectedResult = ResultDTO( - config = LoadedConfigDTO.FORCED_FILE_CONFIG("config").some, - warnings = NodesResponseWaringDTO.NODE_RETURNED_CONFIG_ERROR("n2", "index unknown structure") :: - NodesResponseWaringDTO.NODE_FORCED_FILE_CONFIG("n1") :: - Nil, - error = None, - ) - result shouldEqual expectedResult - } - "be as JSON" in { - val expectedResult = - """ - |{ - | "config" : { - | "raw" : "config", - | "type" : "FORCED_FILE_CONFIG" - | }, - | "warnings" : [ - | { - | "nodeId" : "n2", - | "error" : "index unknown structure", - | "type" : "NODE_RETURNED_CONFIG_ERROR" - | }, - | { - | "nodeId" : "n1", - | "type" : "NODE_FORCED_FILE_CONFIG" - | } - | ], - | "error" : null - |}""".stripMargin - result.asJson shouldEqual io.circe.parser.parse(expectedResult).toTry.get - } - } - } - -} diff --git a/core/src/test/scala/tech/beshu/ror/unit/configuration/loader/SummaryTest.scala b/core/src/test/scala/tech/beshu/ror/unit/configuration/loader/SummaryTest.scala deleted file mode 100644 index 51451dd7ab..0000000000 --- a/core/src/test/scala/tech/beshu/ror/unit/configuration/loader/SummaryTest.scala +++ /dev/null @@ -1,174 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.unit.configuration.loader - -import cats.implicits.* -import eu.timepit.refined.types.string.NonEmptyString -import org.scalatest.matchers.should.Matchers.* -import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.accesscontrol.domain.{IndexName, RorConfigurationIndex} -import tech.beshu.ror.configuration.loader.LoadedRorConfig -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.configuration.loader.distributed.Summary.CurrentNodeHaveToProduceResult -import tech.beshu.ror.configuration.loader.distributed.{NodesResponse, Summary} -import tech.beshu.ror.utils.TestsUtils.unsafeNes - -import scala.language.postfixOps - -class SummaryTest extends AnyWordSpec { - "Summary" when { - "there are no configs" should { - "throw exception, current node have to give response" in { - assertThrows[CurrentNodeHaveToProduceResult.type] { - Summary.create(NodeId(""), Nil, Nil) - } - } - } - "there is no current node config" should { - "throw no current node config error" in { - assertThrows[CurrentNodeHaveToProduceResult.type] { - val conf = LoadedRorConfig.IndexConfig(configIndex("config-index"), "config") - Summary.create(NodeId(""), NodeResponse(NodeId("b"), conf asRight) :: Nil, Nil) - } - } - } - "current node has no ror" should { - "throw error" in { - assertThrows[CurrentNodeHaveToProduceResult.type] { - Summary.create(NodeId("a"), Nil, NodeError(NodeId("a"), NodeError.RorConfigActionNotFound) :: Nil) - } - } - } - "current node has some error" should { - "throw error" in { - Summary.create( - currentNodeId = NodeId("a"), - nodesResponses = Nil, - failures = NodeError(NodeId("a"), NodeError.Unknown("exception message")) :: Nil, - ) shouldEqual - Summary.CurrentNodeResponseError("exception message").asLeft - } - } - "only node returns config" should { - "return current node config" in { - val conf = LoadedRorConfig.IndexConfig(configIndex("config-index"), "config") - Summary.create(NodeId("a"), NodeResponse(NodeId("a"), conf asRight) :: Nil, Nil) shouldBe - Summary.Result(conf, Nil).asRight - } - } - "only node returns error" should { - "return that error" in { - Summary.create( - currentNodeId = NodeId("a"), - nodesResponses = NodeResponse(NodeId("a"), LoadedRorConfig.IndexUnknownStructure asLeft) :: Nil, - failures = Nil, - ) shouldBe - Summary.CurrentNodeConfigError(LoadedRorConfig.IndexUnknownStructure).asLeft - } - } - "current node returns error" should { - "return that error" in { - Summary.create( - currentNodeId = NodeId("a"), - nodesResponses = NodeResponse(NodeId("a"), LoadedRorConfig.IndexUnknownStructure asLeft) :: - NodeResponse(NodeId("b"), LoadedRorConfig.IndexConfig(configIndex("config-index"), "config") asRight) :: - Nil, - failures = Nil, - ) shouldBe - Summary.CurrentNodeConfigError(LoadedRorConfig.IndexUnknownStructure).asLeft - } - } - "other node returns error" should { - "return config, and other node error as warning" in { - val conf = LoadedRorConfig.IndexConfig(configIndex("config-index"), "config") - Summary.create( - currentNodeId = NodeId("b"), - nodesResponses = NodeResponse(NodeId("a"), LoadedRorConfig.IndexUnknownStructure asLeft) :: - NodeResponse(NodeId("b"), conf asRight) :: - Nil, - failures = Nil, - ) shouldBe - Summary.Result(config = conf, - warnings = Summary.NodeReturnedConfigError(NodeId("a"), LoadedRorConfig.IndexUnknownStructure) :: Nil, - ).asRight - } - } - "current node is force loaded from file" should { - "return config, and forced loading from file as warning" in { - val conf = LoadedRorConfig.ForcedFileConfig("config") - Summary.create(NodeId("a"), NodeResponse(NodeId("a"), conf asRight) :: Nil, Nil) shouldBe - Summary.Result(conf, Summary.NodeForcedFileConfig(NodeId("a")) :: Nil).asRight - } - } - "other node returned unknown error" should { - "return config, and unknown error warning" in { - val conf = LoadedRorConfig.ForcedFileConfig("config") - Summary.create( - currentNodeId = NodeId("a"), - nodesResponses = NodeResponse(NodeId("a"), conf asRight) :: Nil, - failures = NodeError(NodeId("b"), NodeError.Unknown("detailed message")) :: Nil, - ) shouldBe - Summary.Result(config = conf, - warnings = Summary.NodeForcedFileConfig(NodeId("a")) :: - Summary.NodeReturnedUnknownError(NodeId("b"), "detailed message") :: - Nil, - ).asRight - } - } - "other node returned action not found" should { - "ignore action not found error" in { - val conf = LoadedRorConfig.ForcedFileConfig("config") - Summary.create(currentNodeId = NodeId("a"), - nodesResponses = NodeResponse(NodeId("a"), conf asRight) :: Nil, - failures = NodeError(NodeId("b"), NodeError.RorConfigActionNotFound) :: Nil, - ) shouldBe - Summary.Result(conf, Summary.NodeForcedFileConfig(NodeId("a")) :: Nil).asRight - } - } - "current node returned timeout" should { - "return error" in { - Summary.create(NodeId("a"), Nil, NodeError(NodeId("a"), NodeError.Timeout) :: Nil) shouldBe - Summary.CurrentNodeResponseTimeoutError.asLeft - } - } - "other node has timeout" should { - "return config, and warning" in { - val currentConfig = LoadedRorConfig.FileConfig("config1") - Summary.create(currentNodeId = NodeId("a"), - nodesResponses = NodeResponse(NodeId("a"), currentConfig asRight) :: Nil, - failures = NodeError(NodeId("b"), NodesResponse.NodeError.Timeout) :: Nil, - ) shouldBe - Summary.Result(currentConfig, Summary.NodeResponseTimeoutWarning(NodeId("b")) :: Nil).asRight - } - } - "other node has different config, than current node" should { - "return config, and warning" in { - val currentConfig = LoadedRorConfig.FileConfig("config1") - val otherConfig = LoadedRorConfig.FileConfig("config2") - Summary.create( - currentNodeId = NodeId("a"), - nodesResponses = NodeResponse(NodeId("a"), currentConfig asRight) :: - NodeResponse(NodeId("b"), otherConfig asRight) :: - Nil, failures = Nil, - ) shouldBe - Summary.Result(currentConfig, Summary.NodeReturnedDifferentConfig(NodeId("b"), otherConfig) :: Nil).asRight - } - } - } - - private def configIndex(value: NonEmptyString) = RorConfigurationIndex(IndexName.Full(value)) -} diff --git a/core/src/test/scala/tech/beshu/ror/unit/settings/es/RorBootSettingsTest.scala b/core/src/test/scala/tech/beshu/ror/unit/settings/es/RorBootSettingsTest.scala new file mode 100644 index 0000000000..b1c3e75eb7 --- /dev/null +++ b/core/src/test/scala/tech/beshu/ror/unit/settings/es/RorBootSettingsTest.scala @@ -0,0 +1,110 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.unit.settings.es + +import monix.execution.Scheduler.Implicits.global +import org.scalatest.Inside +import org.scalatest.matchers.should.Matchers.* +import org.scalatest.wordspec.AnyWordSpec +import tech.beshu.ror.SystemContext +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError.MalformedSettings +import tech.beshu.ror.utils.TestsUtils.{defaultEsVersionForTests, getResourcePath, testEsNodeSettings} + +class RorBootSettingsTest + extends AnyWordSpec with Inside { + + private implicit val systemContext: SystemContext = SystemContext.default + + "A ReadonlyREST ES starting settings" should { + "be loaded from elasticsearch config file" when { + "boot settings contains not started response code" in { + val settings = RorBootSettings + .load(esEnvFrom("/boot_tests/boot_config/not_started_code_defined")) + .runSyncUnsafe() + + settings.map(_.rorNotStartedResponse) should be(Right( + RorNotStartedResponse(RorNotStartedResponse.HttpCode.`503`) + )) + } + "boot settings contains failed to start response code" in { + val settings = RorBootSettings + .load(esEnvFrom("/boot_tests/boot_config/failed_to_start_code_defined")) + .runSyncUnsafe() + + settings.map(_.rorFailedToStartResponse) should be(Right( + RorFailedToStartResponse(RorFailedToStartResponse.HttpCode.`503`) + )) + } + "boot settings contains all codes" in { + val settings = RorBootSettings + .load(esEnvFrom("/boot_tests/boot_config/all_codes_defined")) + .runSyncUnsafe() + + settings should be(Right(RorBootSettings( + rorNotStartedResponse = RorNotStartedResponse(RorNotStartedResponse.HttpCode.`403`), + rorFailedToStartResponse = RorFailedToStartResponse(RorFailedToStartResponse.HttpCode.`503`), + ))) + } + } + "there is no response codes defined in config, default values should be used" in { + val settings = RorBootSettings + .load(esEnvFrom("/boot_tests/boot_config")) + .runSyncUnsafe() + + settings should be(Right(RorBootSettings( + rorNotStartedResponse = RorNotStartedResponse(RorNotStartedResponse.HttpCode.`403`), + rorFailedToStartResponse = RorFailedToStartResponse(RorFailedToStartResponse.HttpCode.`403`), + ))) + } + } + "not be able to load" when { + "not started response code is malformed" in { + val esConfigFolderPath = "/boot_tests/boot_config/not_started_code_malformed" + val expectedFilePath = getResourcePath(s"$esConfigFolderPath/elasticsearch.yml") + + RorBootSettings.load(esEnvFrom(esConfigFolderPath)).runSyncUnsafe() shouldBe Left { + MalformedSettings( + expectedFilePath, + s"Cannot load ROR boot settings from file ${expectedFilePath.toString}. " + + s"Cause: Unsupported response code [200] for readonlyrest.not_started_response_code. Supported response codes are: 403, 503." + ) + } + } + "failed to start response code is malformed" in { + val esConfigFolderPath = "/boot_tests/boot_config/failed_to_start_code_malformed" + val expectedFilePath = getResourcePath(s"$esConfigFolderPath/elasticsearch.yml") + + RorBootSettings.load(esEnvFrom(esConfigFolderPath)).runSyncUnsafe() shouldBe Left { + MalformedSettings( + expectedFilePath, + s"Cannot load ROR boot settings from file ${expectedFilePath.toString}. " + + s"Cause: Unsupported response code [200] for readonlyrest.failed_to_start_response_code. Supported response codes are: 403, 503." + ) + } + } + } + + private def esEnvFrom(resourceEsConfigFolderPath: String) = EsEnv( + getResourcePath(resourceEsConfigFolderPath), + getResourcePath(resourceEsConfigFolderPath), + defaultEsVersionForTests, + testEsNodeSettings + ) +} diff --git a/core/src/test/scala/tech/beshu/ror/unit/settings/es/RorSslSettingsTest.scala b/core/src/test/scala/tech/beshu/ror/unit/settings/es/RorSslSettingsTest.scala new file mode 100644 index 0000000000..73f5ab8fe7 --- /dev/null +++ b/core/src/test/scala/tech/beshu/ror/unit/settings/es/RorSslSettingsTest.scala @@ -0,0 +1,231 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.unit.settings.es + +import better.files.File +import monix.execution.Scheduler.Implicits.global +import org.scalatest.Inside +import org.scalatest.matchers.should.Matchers.* +import org.scalatest.wordspec.AnyWordSpec +import tech.beshu.ror.SystemContext +import tech.beshu.ror.accesscontrol.domain.RorSettingsFile +import tech.beshu.ror.es.EsEnv +import tech.beshu.ror.settings.es.RorSslSettings +import tech.beshu.ror.settings.es.SslSettings.* +import tech.beshu.ror.settings.es.SslSettings.ServerCertificateSettings.{FileBasedSettings, KeystoreBasedSettings} +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError.MalformedSettings +import tech.beshu.ror.utils.TestsPropertiesProvider +import tech.beshu.ror.utils.TestsUtils.{defaultEsVersionForTests, getResourcePath, testEsNodeSettings} + +class RorSslSettingsTest + extends AnyWordSpec with Inside { + + private implicit val systemContext: SystemContext = new SystemContext( + propertiesProvider = TestsPropertiesProvider.default + ) + + "A ReadonlyREST ES API SSL settings" should { + "be loaded from elasticsearch config file" when { + "all properties contain at least one non-digit" in { + val ssl = forceLoadRorSslSettings("/boot_tests/es_api_ssl_settings_in_elasticsearch_config") + inside(ssl.externalSsl) { + case Some(ExternalSslSettings(KeystoreBasedSettings(keystoreFile, Some(keystorePassword), None, Some(keyPass)), Some(ClientCertificateSettings.TruststoreBasedSettings(truststoreFile, Some(truststorePassword))), allowedProtocols, allowedCiphers, clientAuthenticationEnabled, FipsMode.NonFips)) => + keystoreFile.value.name should be("ror-keystore.jks") + keystorePassword should be(KeystorePassword("readonlyrest1")) + keyPass should be(KeyPass("readonlyrest2")) + truststoreFile.value.name should be("ror-truststore.jks") + truststorePassword should be(TruststorePassword("readonlyrest3")) + allowedProtocols should be(Set.empty) + allowedCiphers should be(Set.empty) + clientAuthenticationEnabled should be(false) + } + ssl.internodeSsl should be(None) + } + "some properties contains only digits" in { + val ssl = forceLoadRorSslSettings("/boot_tests/es_api_ssl_settings_in_elasticsearch_config_only_digits") + inside(ssl.externalSsl) { + case Some(ExternalSslSettings(KeystoreBasedSettings(keystoreFile, Some(keystorePassword), None, Some(keyPass)), Some(ClientCertificateSettings.TruststoreBasedSettings(truststoreFile, Some(truststorePassword))), allowedProtocols, allowedCiphers, clientAuthenticationEnabled, FipsMode.NonFips)) => + keystoreFile.value.name should be("ror-keystore.jks") + keystorePassword should be(KeystorePassword("123456")) + keyPass should be(KeyPass("12")) + truststoreFile.value.name should be("ror-truststore.jks") + truststorePassword should be(TruststorePassword("1234")) + allowedProtocols should be(Set.empty) + allowedCiphers should be(Set.empty) + clientAuthenticationEnabled should be(false) + } + ssl.internodeSsl should be(None) + } + "server and client are configured using pem files" in { + val ssl = forceLoadRorSslSettings("/boot_tests/es_api_ssl_settings_pem_files") + inside(ssl.externalSsl) { + case Some(ExternalSslSettings(FileBasedSettings(serverCertificateKeyFile, serverCertificateFile), Some(ClientCertificateSettings.FileBasedSettings(clientTrustedCertificateFile)), allowedProtocols, allowedCiphers, clientAuthenticationEnabled, FipsMode.NonFips)) => + serverCertificateKeyFile.value.name should be("server_certificate_key.pem") + serverCertificateFile.value.name should be("server_certificate.pem") + clientTrustedCertificateFile.value.name should be("client_certificate.pem") + allowedProtocols should be(Set.empty) + allowedCiphers should be(Set.empty) + clientAuthenticationEnabled should be(false) + } + } + } + "be loaded from readonlyrest config file" when { + "elasticsearch config file doesn't contain ROR ssl section" in { + val ssl = forceLoadRorSslSettings("/boot_tests/es_api_ssl_settings_in_readonlyrest_settings") + inside(ssl.externalSsl) { + case Some(ExternalSslSettings(KeystoreBasedSettings(keystoreFile, Some(keystorePassword), None, Some(keyPass)), Some(ClientCertificateSettings.TruststoreBasedSettings(truststoreFile, Some(truststorePassword))), allowedProtocols, allowedCiphers, clientAuthenticationEnabled, FipsMode.NonFips)) => + keystoreFile.value.name should be("ror-keystore.jks") + keystorePassword should be(KeystorePassword("readonlyrest1")) + keyPass should be(KeyPass("readonlyrest2")) + truststoreFile.value.name should be("ror-truststore.jks") + truststorePassword should be(TruststorePassword("readonlyrest3")) + allowedProtocols should be(Set.empty) + allowedCiphers should be(Set.empty) + clientAuthenticationEnabled should be(false) + } + ssl.internodeSsl should be(None) + } + } + "be disabled" when { + "no ssl section is provided" in { + val ssl = loadRorSslSettings("/boot_tests/no_es_api_ssl_settings") + ssl should be (Right(None)) + } + "it's disabled by proper settings" in { + val ssl = loadRorSslSettings("/boot_tests/es_api_ssl_settings_disabled") + ssl should be (Right(None)) + } + } + "not be able to load" when { + "SSL settings are malformed" when { + "keystore_file entry is missing" in { + val esConfigFolderPath = "/boot_tests/es_api_ssl_settings_malformed" + val expectedFilePath = getResourcePath(s"$esConfigFolderPath/elasticsearch.yml") + loadRorSslSettings(esConfigFolderPath) shouldBe Left { + MalformedSettings(expectedFilePath, s"Cannot load ROR SSL settings from file ${expectedFilePath.toString}. Cause: Missing required field") + } + } + } + "file content is not valid yaml" in { + val error = loadRorSslSettings("/boot_tests/es_api_ssl_settings_file_invalid_yaml/") + inside(error) { + case Left(error: LoadingError.MalformedSettings) => + error.message should startWith("Cannot parse file") + } + } + "SSL settings contain both pem and truststore based configuration" in { + val configFolderPath = "/boot_tests/es_api_ssl_settings_both_pem_and_keystore_configured" + val expectedFilePath = getResourcePath(s"$configFolderPath/elasticsearch.yml") + + loadRorSslSettings(configFolderPath) shouldBe Left { + MalformedSettings( + expectedFilePath, + s"Cannot load ROR SSL settings from file ${expectedFilePath.toString}. " + + s"Cause: Field sets [server_certificate_key_file, server_certificate_file] and [keystore_file, keystore_pass, key_pass, key_alias] could not be present in the same settings section") + } + } + } + } + + "A ReadonlyREST internode SSL settings" should { + "be loaded from elasticsearch config file" in { + val ssl = forceLoadRorSslSettings("/boot_tests/internode_ssl_settings_in_elasticsearch_config") + inside(ssl.internodeSsl) { + case Some(InternodeSslSettings(KeystoreBasedSettings(keystoreFile, Some(keystorePassword), None, Some(keyPass)), truststoreConfiguration, allowedProtocols, allowedCiphers, clientAuthenticationEnabled, certificateVerificationEnabled, hostnameVerificationEnabled, FipsMode.NonFips)) => + keystoreFile.value.name should be("ror-keystore.jks") + keystorePassword should be(KeystorePassword("readonlyrest1")) + keyPass should be(KeyPass("readonlyrest2")) + truststoreConfiguration should be(None) + allowedProtocols should be(Set.empty) + allowedCiphers should be(Set.empty) + clientAuthenticationEnabled should be(false) + certificateVerificationEnabled should be(true) + hostnameVerificationEnabled should be(true) + } + ssl.externalSsl should be(None) + } + "be loaded from elasticsearch config file when pem files are used" in { + val ssl = forceLoadRorSslSettings("/boot_tests/internode_ssl_settings_pem_files") + inside(ssl.internodeSsl) { + case Some(InternodeSslSettings(FileBasedSettings(serverCertificateKeyFile, serverCertificateFile), Some(ClientCertificateSettings.FileBasedSettings(clientTrustedCertificateFile)), allowedProtocols, allowedCiphers, clientAuthenticationEnabled, certificateVerificationEnabled, hostnameVerificationEnabled, FipsMode.NonFips)) => + serverCertificateKeyFile.value.name should be("server_certificate_key.pem") + serverCertificateFile.value.name should be("server_certificate.pem") + clientTrustedCertificateFile.value.name should be("client_certificate.pem") + allowedProtocols should be(Set.empty) + allowedCiphers should be(Set.empty) + clientAuthenticationEnabled should be(false) + certificateVerificationEnabled should be(true) + hostnameVerificationEnabled should be(false) + } + } + "be loaded from readonlyrest settings file" when { + "elasticsearch config file doesn't contain ROR ssl section" in { + val ssl = forceLoadRorSslSettings("/boot_tests/internode_ssl_settings_in_readonlyrest_settings") + inside(ssl.internodeSsl) { + case Some(InternodeSslSettings(KeystoreBasedSettings(keystoreFile, Some(keystorePassword), None, Some(keyPass)), Some(ClientCertificateSettings.TruststoreBasedSettings(truststoreFile, Some(truststorePassword))), allowedProtocols, allowedCiphers, clientAuthenticationEnabled, certificateVerificationEnabled, hostnameVerificationEnabled, FipsMode.NonFips)) => + keystoreFile.value.name should be("ror-keystore.jks") + keystorePassword should be(KeystorePassword("readonlyrest1")) + keyPass should be(KeyPass("readonlyrest2")) + truststoreFile.value.name should be("ror-truststore.jks") + truststorePassword should be(TruststorePassword("readonlyrest3")) + allowedProtocols should be(Set.empty) + allowedCiphers should be(Set.empty) + clientAuthenticationEnabled should be(false) + certificateVerificationEnabled should be(true) + hostnameVerificationEnabled should be(true) + } + ssl.externalSsl should be(None) + } + } + "be disabled" when { + "no ssl section is provided" in { + val ssl = loadRorSslSettings("/boot_tests/no_internode_ssl_settings") + ssl should be (Right(None)) + } + "it's disabled by proper settings" in { + val ssl = loadRorSslSettings("/boot_tests/internode_ssl_settings_disabled") + ssl should be (Right(None)) + } + } + "not be able to load" when { + "SSL settings are malformed" when { + "keystore_file entry is missing" in { + val configFolderPath = "/boot_tests/internode_ssl_settings_malformed" + val expectedFilePath = getResourcePath(s"$configFolderPath/elasticsearch.yml") + loadRorSslSettings(configFolderPath) shouldBe Left { + MalformedSettings(expectedFilePath, s"Cannot load ROR SSL settings from file ${expectedFilePath.toString}. Cause: Missing required field") + } + } + } + } + } + + private def forceLoadRorSslSettings(settingsFolderPath: String) = { + loadRorSslSettings(settingsFolderPath) + .toOption.flatten.get + } + + private def loadRorSslSettings(settingsFolderPath: String) = { + val esConfigFile = File(getResourcePath(settingsFolderPath)) + val rorSettingsFile = RorSettingsFile(getResourcePath(s"$settingsFolderPath/readonlyrest.yml")) + val esEnv = EsEnv(esConfigFile, esConfigFile, defaultEsVersionForTests, testEsNodeSettings) + RorSslSettings + .load(esEnv, rorSettingsFile) + .runSyncUnsafe() + } +} diff --git a/core/src/test/scala/tech/beshu/ror/unit/settings/es/YamlFileBasedSettingsLoaderTest.scala b/core/src/test/scala/tech/beshu/ror/unit/settings/es/YamlFileBasedSettingsLoaderTest.scala new file mode 100644 index 0000000000..85529a4cbc --- /dev/null +++ b/core/src/test/scala/tech/beshu/ror/unit/settings/es/YamlFileBasedSettingsLoaderTest.scala @@ -0,0 +1,79 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.unit.settings.es + +import better.files.File +import cats.implicits.* +import io.circe.Decoder +import monix.execution.Scheduler.Implicits.global +import org.scalatest.Inside +import org.scalatest.matchers.should.Matchers.* +import org.scalatest.wordspec.AnyWordSpec +import tech.beshu.ror.SystemContext +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader +import tech.beshu.ror.settings.es.YamlFileBasedSettingsLoader.LoadingError + +class YamlFileBasedSettingsLoaderTest extends AnyWordSpec with Inside { + + private implicit val systemContext: SystemContext = new SystemContext( + envVarsProvider = name => + name.value.value match { + case "USER_NAME" => Some("John") + case _ => None + } + ) + + "YamlFileBasedConfigLoader" should { + "decode file file" in { + val result = loadFromTempFile[String](""""encoded value"""") + result shouldBe "encoded value".asRight + } + "decode file with variable" in { + val result = loadFromTempFile[String](""""${USER_NAME}"""") + result shouldBe "John".asRight + } + "decode file with variable with transformation" in { + val result = loadFromTempFile[String](""""${USER_NAME}#{to_uppercase}"""") + result shouldBe "JOHN".asRight + } + "fail for non-existing variable" in { + val result = loadFromTempFile[String](""""${WRONG_VARIABLE}"""") + inside(result) { + case Left(error: LoadingError.MalformedSettings) => + error.message should include("WRONG_VARIABLE") + } + } + "fail for non-existing file" in { + val loader = new YamlFileBasedSettingsLoader(File("non-existing-file")) + val result = loader.loadSettings[String]("TEST").runSyncUnsafe() + inside(result) { + case Left(error: LoadingError.FileNotFound) => + } + } + } + + private def loadFromTempFile[A: Decoder](content: String) = + tempFile(content) + .map { file => + val loader = new YamlFileBasedSettingsLoader(file) + loader.loadSettings[A]("TEST").runSyncUnsafe() + } + .get() + + private def tempFile(content: String) = File.temporaryFile().map(_.write(content)) + +} diff --git a/core/src/test/scala/tech/beshu/ror/unit/utils/MatcherWithWildcardsScalaTest.scala b/core/src/test/scala/tech/beshu/ror/unit/utils/MatcherWithWildcardsScalaTest.scala index 4ba8352121..f3e6e707e8 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/utils/MatcherWithWildcardsScalaTest.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/utils/MatcherWithWildcardsScalaTest.scala @@ -123,14 +123,18 @@ class MatcherWithWildcardsScalaTest "filter result" should { "reject not related haystack" in { forAll("matchers", "haystack") { (matchers: List[String], haystack: Set[String]) => - val matcher = PatternsMatcher.create(matchers)(caseSensitiveStringMatchable) + // Filter out wildcard characters to ensure literal string matching + val literalMatchers = matchers.filter(m => !m.contains('*') && !m.contains('?')) + val matcher = PatternsMatcher.create(literalMatchers)(caseSensitiveStringMatchable) val result = matcher.filter(haystack) - result shouldBe haystack.intersect(matchers.toCovariantSet) + result shouldBe haystack.intersect(literalMatchers.toCovariantSet) } } "contain all haystack if haystack is in matchers" in { forAll("matchers", "haystack") { (matchers: List[String], haystack: Set[String]) => - val matcher = PatternsMatcher.create(matchers ++ haystack)(caseSensitiveStringMatchable) + // Filter out wildcard characters to ensure literal string matching + val literalMatchers = matchers.filter(m => !m.contains('*') && !m.contains('?')) + val matcher = PatternsMatcher.create(literalMatchers ++ haystack)(caseSensitiveStringMatchable) val result = matcher.filter(haystack) result shouldBe haystack } diff --git a/core/src/test/scala/tech/beshu/ror/unit/utils/RorYamlParserTests.scala b/core/src/test/scala/tech/beshu/ror/unit/utils/RorYamlParserTests.scala index 3e20fb30aa..558aaa5ef0 100644 --- a/core/src/test/scala/tech/beshu/ror/unit/utils/RorYamlParserTests.scala +++ b/core/src/test/scala/tech/beshu/ror/unit/utils/RorYamlParserTests.scala @@ -16,20 +16,20 @@ */ package tech.beshu.ror.unit.utils -import io.circe.{Json, ParsingFailure} +import io.circe.Json import org.scalatest.Inside import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec import squants.information.{Bytes, Kilobytes} import tech.beshu.ror.utils.TestsUtils.* -import tech.beshu.ror.utils.yaml.RorYamlParser +import tech.beshu.ror.utils.yaml.YamlParser class RorYamlParserTests extends AnyWordSpec with Inside with Matchers { "Yaml parser" should { "return parsing failure error" when { - "readonlyrest config has duplicated 'readonlyrest' section" in { - val result = rorConfigFrom( + "ROR settings has duplicated 'readonlyrest' section" in { + val result = rorSettingFrom( """ |readonlyrest: | @@ -56,8 +56,8 @@ class RorYamlParserTests extends AnyWordSpec with Inside with Matchers { failure.message should be("Duplicated key: 'readonlyrest'") } } - "readonlyrest config has duplicated 'access_control_rules' section" in { - val result = rorConfigFrom( + "ROR settings has duplicated 'access_control_rules' section" in { + val result = rorSettingFrom( """ |readonlyrest: | @@ -82,8 +82,8 @@ class RorYamlParserTests extends AnyWordSpec with Inside with Matchers { failure.message should be("Duplicated key: 'access_control_rules'") } } - "readonlyrest config has duplicated definition" in { - val result = rorConfigFrom( + "ROR settings has duplicated definition" in { + val result = rorSettingFrom( """ |readonlyrest: | @@ -112,7 +112,7 @@ class RorYamlParserTests extends AnyWordSpec with Inside with Matchers { } } "block has duplicated rule" in { - val result = rorConfigFrom( + val result = rorSettingFrom( """ |readonlyrest: | @@ -132,7 +132,7 @@ class RorYamlParserTests extends AnyWordSpec with Inside with Matchers { } } } - "return valid ror config" when { + "return valid ror settings" when { "none of the keys is duplicated within its scope" in { val rawConfig = """ @@ -160,10 +160,10 @@ class RorYamlParserTests extends AnyWordSpec with Inside with Matchers { | |""".stripMargin - val result = rorConfigFrom(rawConfig) + val result = rorSettingFrom(rawConfig) inside(result) { - case Right(config) => config.raw shouldBe rawConfig + case Right(settings) => settings.rawYaml shouldBe rawConfig } } } @@ -196,7 +196,7 @@ class RorYamlParserTests extends AnyWordSpec with Inside with Matchers { | auth_key: "admin:container" |""".stripMargin - val result = new RorYamlParser(Bytes(10)).parse(yamlContent) + val result = new YamlParser(Some(Bytes(10))).parse(yamlContent) inside(result) { case Left(parsingFailure) => parsingFailure.message should be("The incoming YAML document exceeds the limit: 10 code points.") @@ -205,5 +205,5 @@ class RorYamlParserTests extends AnyWordSpec with Inside with Matchers { } private def parseYaml(yamlContent: String): Json = - new RorYamlParser(Kilobytes(100)).parse(yamlContent).toTry.get + new YamlParser(Some(Kilobytes(100))).parse(yamlContent).toTry.get } diff --git a/core/src/test/scala/tech/beshu/ror/unit/utils/WithReadonlyrestBootSupport.scala b/core/src/test/scala/tech/beshu/ror/unit/utils/WithReadonlyrestBootSupport.scala new file mode 100644 index 0000000000..6e9b371c42 --- /dev/null +++ b/core/src/test/scala/tech/beshu/ror/unit/utils/WithReadonlyrestBootSupport.scala @@ -0,0 +1,57 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.unit.utils + +import cats.effect.Resource +import monix.eval.Task +import monix.execution.Scheduler.Implicits.global +import org.scalatest.Suite +import tech.beshu.ror.boot.{ReadonlyRest, RorInstance} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings + +trait WithReadonlyrestBootSupport { + this: Suite => + + protected def withReadonlyRest(readonlyRestAndSettings: (ReadonlyRest, EsConfigBasedRorSettings)) + (testCode: RorInstance => Any): Unit = { + val (readonlyRest, esConfigBasedRorSettings) = readonlyRestAndSettings + withReadonlyRestExt((readonlyRest, esConfigBasedRorSettings, ())) { case (rorInstance, ()) => testCode(rorInstance) } + } + + protected def withReadonlyRestExt[EXT](readonlyRestAndSettingsAndExt: (ReadonlyRest, EsConfigBasedRorSettings, EXT)) + (testCode: (RorInstance, EXT) => Any): Unit = { + val (readonlyRest, esConfigBasedRorSettings, ext) = readonlyRestAndSettingsAndExt + Resource + .make( + acquire = readonlyRest + .start(esConfigBasedRorSettings) + .flatMap { + case Right(startedInstance) => Task.now(startedInstance) + case Left(startingFailure) => Task.raiseError(new Exception(s"$startingFailure")) + } + )( + release = _.stop() + ) + .use { startedInstance => + Task.delay { + testCode(startedInstance, ext) + } + } + .runSyncUnsafe() + } + +} diff --git a/core/src/test/scala/tech/beshu/ror/utils/TestsUtils.scala b/core/src/test/scala/tech/beshu/ror/utils/TestsUtils.scala index 664d7503e9..ef5f0b3922 100644 --- a/core/src/test/scala/tech/beshu/ror/utils/TestsUtils.scala +++ b/core/src/test/scala/tech/beshu/ror/utils/TestsUtils.scala @@ -19,14 +19,14 @@ package tech.beshu.ror.utils import better.files.File import cats.data.{EitherT, NonEmptyList} import eu.timepit.refined.types.string.NonEmptyString -import io.circe.ParsingFailure +import io.circe.{Json, ParsingFailure, parser} import io.jsonwebtoken.JwtBuilder import io.lemonlabs.uri.Url import monix.eval.Task import monix.execution.Scheduler import org.scalatest.matchers.should.Matchers.* import squants.information.Megabytes -import tech.beshu.ror.accesscontrol.audit.{AuditEnvironmentContextBasedOnEsNodeSettings, LoggingContext} +import tech.beshu.ror.accesscontrol.audit.LoggingContext import tech.beshu.ror.accesscontrol.blocks.BlockContext.* import tech.beshu.ror.accesscontrol.blocks.definitions.ImpersonatorDef.ImpersonatedUsers import tech.beshu.ror.accesscontrol.blocks.definitions.UserDef.GroupMappings @@ -50,15 +50,14 @@ import tech.beshu.ror.accesscontrol.domain.GroupIdLike.GroupId import tech.beshu.ror.accesscontrol.domain.Header.Name import tech.beshu.ror.accesscontrol.domain.KibanaApp.KibanaAppRegex import tech.beshu.ror.accesscontrol.domain.User.UserIdPattern -import tech.beshu.ror.audit.AuditEnvironmentContext -import tech.beshu.ror.configuration.RawRorConfig import tech.beshu.ror.es.{EsNodeSettings, EsVersion} +import tech.beshu.ror.settings.ror.RawRorSettings import tech.beshu.ror.syntax.* import tech.beshu.ror.utils.js.{JsCompiler, MozillaJsCompiler} import tech.beshu.ror.utils.json.JsonPath import tech.beshu.ror.utils.misc.JwtUtils import tech.beshu.ror.utils.uniquelist.{UniqueList, UniqueNonEmptyList} -import tech.beshu.ror.utils.yaml.RorYamlParser +import tech.beshu.ror.utils.yaml.YamlParser import java.nio.file.Path import java.time.Duration @@ -70,11 +69,11 @@ import scala.util.{Failure, Success} object TestsUtils { implicit val loggingContext: LoggingContext = LoggingContext(Set.empty) - val rorYamlParser = new RorYamlParser(Megabytes(3)) + val rorYamlParser = new YamlParser(Some(Megabytes(3))) val defaultEsVersionForTests: EsVersion = EsVersion(8, 17, 0) - inline def nes(str : String): NonEmptyString = RefinedUtils.nes(str) + inline def nes(str: String): NonEmptyString = RefinedUtils.nes(str) def basicAuthHeader(value: String): Header = new Header( @@ -85,26 +84,23 @@ object TestsUtils { def bearerHeader(jwt: JwtUtils.Jwt): Header = bearerHeader(Header.Name.authorization.value, jwt) - def bearerHeader(headerName: NonEmptyString, jwt: JwtUtils.Jwt): Header = - new Header( - Header.Name(headerName), - NonEmptyString.unsafeFrom(s"Bearer ${jwt.stringify()}") - ) + def bearerHeader(headerName: NonEmptyString, jwt: JwtUtils.Jwt): Header = new Header( + Header.Name(headerName), + NonEmptyString.unsafeFrom(s"Bearer ${jwt.stringify()}") + ) - def bearerHeader(jwt: JwtBuilder): Header = - new Header( - Header.Name.authorization, - NonEmptyString.unsafeFrom(s"Bearer ${jwt.compact}") - ) + def bearerHeader(jwt: JwtBuilder): Header = new Header( + Header.Name.authorization, + NonEmptyString.unsafeFrom(s"Bearer ${jwt.compact}") + ) def impersonationHeader(username: NonEmptyString): Header = new Header(Header.Name.impersonateAs, username) - def header(name: String, value: String): Header = - new Header( - Name(NonEmptyString.unsafeFrom(name)), - NonEmptyString.unsafeFrom(value) - ) + def header(name: String, value: String): Header = new Header( + Name(NonEmptyString.unsafeFrom(name)), + NonEmptyString.unsafeFrom(value) + ) def currentGroupHeader(value: String): Header = header("x-ror-current-group", value) @@ -380,18 +376,18 @@ object TestsUtils { def nel: NonEmptyList[T] = NonEmptyList.one(value) } - def rorConfigFromUnsafe(yamlContent: String): RawRorConfig = { - rorConfigFrom(yamlContent).toOption.get + def rorSettingsFromUnsafe(yamlContent: String): RawRorSettings = { + rorSettingFrom(yamlContent).toOption.get } - def rorConfigFrom(yamlContent: String): Either[ParsingFailure, RawRorConfig] = { + def rorSettingFrom(yamlContent: String): Either[ParsingFailure, RawRorSettings] = { rorYamlParser .parse(yamlContent) - .map(json => RawRorConfig(json, yamlContent)) + .map(json => RawRorSettings(json, yamlContent)) } - def rorConfigFromResource(resource: String): RawRorConfig = { - rorConfigFromUnsafe { + def rorSettingsFromResource(resource: String): RawRorSettings = { + rorSettingsFromUnsafe { getResourceContent(resource) } } @@ -404,13 +400,15 @@ object TestsUtils { File(getResourcePath(resource)).contentAsString } + def circeJsonFrom(jsonString: String): Json = { + parser.parse(jsonString).toTry.get + } + def testEsNodeSettings: EsNodeSettings = EsNodeSettings( clusterName = "testEsCluster", nodeName = "testEsNode" ) - def testAuditEnvironmentContext: AuditEnvironmentContext = new AuditEnvironmentContextBasedOnEsNodeSettings(testEsNodeSettings) - implicit class ValueOrIllegalState[ERROR, SUCCESS](private val eitherT: EitherT[Task, ERROR, SUCCESS]) extends AnyVal { def valueOrThrowIllegalState()(implicit scheduler: Scheduler): SUCCESS = { @@ -420,7 +418,7 @@ object TestsUtils { } } } - + implicit def unsafeNes(str: String): NonEmptyString = NonEmptyString.unsafeFrom(str) def userIdPatterns(id: String, ids: String*): UserIdPatterns = { diff --git a/development.md b/development.md index a31af09d46..38987ecdac 100644 --- a/development.md +++ b/development.md @@ -46,8 +46,8 @@ Currently eshome support debugging only es8x modules. * ROR plugin binaries can be found in `es70x/build/distributions/` **⚠️Required tools:** -* OpenJDK 21 -* Gradle 8.4 +* Java 17 +* Gradle ### Using Docker * `./docker-based-builder/build.sh [ES_VERSION_1] [ES_VERSION_1] ... [ES_VERSION_N]` diff --git a/docs/api/ror-internal-api-swagger.yaml b/docs/api/ror-internal-api-swagger.yaml index bca4f10ded..3b237a1249 100644 --- a/docs/api/ror-internal-api-swagger.yaml +++ b/docs/api/ror-internal-api-swagger.yaml @@ -1508,7 +1508,7 @@ components: warnings: - block_name: other rule_name: auth_key_sha1 - message: "The rule contains fully hashed username and password. It doesn't support impersonation in this configuration" + message: "The rule contains fully hashed username and password. It doesn't support impersonation in this use case." hint: "You can use second version of the rule and use not hashed username. Like that: `auth_key_sha1: USER_NAME:hash(PASSWORD)" TestSettingsNotConfiguredResponseExample: @@ -1543,7 +1543,7 @@ components: warnings: - block_name: other rule_name: auth_key_sha1 - message: "The rule contains fully hashed username and password. It doesn't support impersonation in this configuration" + message: "The rule contains fully hashed username and password. It doesn't support impersonation in this use case." hint: "You can use second version of the rule and use not hashed username. Like that: `auth_key_sha1: USER_NAME:hash(PASSWORD)" TestSettingsUpdateFailedResponseExample: diff --git a/es67x/build.gradle b/es67x/build.gradle index cf21673cb9..1d4580de09 100644 --- a/es67x/build.gradle +++ b/es67x/build.gradle @@ -101,7 +101,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es67x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es67x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 6678bb7ee0..b791d55d2d 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -27,6 +27,7 @@ import org.elasticsearch.snapshots.SnapshotsService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, IndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -34,12 +35,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo} import tech.beshu.ror.implicits.* @@ -58,20 +59,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], snapshotsServiceSupplier: Supplier[Option[SnapshotsService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -213,7 +214,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es67x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es67x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index bada656ff4..b7b3132efd 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.MapperService import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService import org.elasticsearch.threadpool.ThreadPool @@ -49,25 +50,24 @@ import org.elasticsearch.transport.netty4.Netty4Utils import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,19 +96,19 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -128,7 +128,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, SnapshotsServiceInterceptor.snapshotsServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,16 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) xContentRegistry: NamedXContentRegistry, networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = doPrivileged { - new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl) } - ) + } .toMap .asJava } @@ -186,16 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = doPrivileged { - new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl) } - ) + } .toMap .asJava } @@ -208,8 +202,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -229,8 +222,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(settings, restController), new RestRRAuthMockAction(settings, restController), - new RestRRTestConfigAction(settings, restController), - new RestRRConfigAction(settings, restController, nodesInCluster), + new RestRRTestSettingsAction(settings, restController), new RestRRUserMetadataAction(settings, restController), new RestRRAuditEventAction(settings, restController) ).asJava diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index e4e2a2c4be..8c4d3593e0 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -27,7 +27,7 @@ class RRAdminActionType extends Action[RRAdminRequest, RRAdminResponse, RRAdminR override def newResponse(): RRAdminResponse = new RRAdminResponse } object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() } diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index 4d3619d24f..e1486b99b6 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") def this() = { @@ -42,19 +42,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7e32e42c62..cb8e25ab99 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -19,11 +19,11 @@ package tech.beshu.ror.es.actions.rradmin import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { def this() = { @@ -32,24 +32,24 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index b049809692..18b5e7ffa1 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.es.actions.rradmin.rest import org.elasticsearch.client.node.NodeClient import org.elasticsearch.common.inject.Inject import org.elasticsearch.common.settings.Settings -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import tech.beshu.ror.constants import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, RRAdminRequest, RRAdminResponse} import tech.beshu.ror.es.utils.RestToXContentWithStatusListener @@ -29,10 +29,10 @@ import tech.beshu.ror.es.utils.RestToXContentWithStatusListener class RestRRAdminAction(settings: Settings, controller: RestController) extends BaseRestHandler(settings) with RestHandler { - register("POST", constants.FORCE_RELOAD_CONFIG_PATH) - register("GET", constants.PROVIDE_INDEX_CONFIG_PATH) - register("POST", constants.UPDATE_INDEX_CONFIG_PATH) - register("GET", constants.PROVIDE_FILE_CONFIG_PATH) + register("POST", constants.FORCE_RELOAD_SETTINGS_PATH) + register("GET", constants.PROVIDE_INDEX_SETTINGS_PATH) + register("POST", constants.UPDATE_INDEX_SETTINGS_PATH) + register("GET", constants.PROVIDE_FILE_SETTINGS_PATH) override val getName: String = "ror-admin-handler" diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index c0257cc82c..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private NodeConfig nodeConfig; - - public RRConfig() { - super(); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 5bf1c513d9..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,38 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.{Action, ActionRequestBuilder} -import org.elasticsearch.client.ElasticsearchClient -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends Action[RRConfigsRequest, RRConfigsResponse, RRConfigActionType.RequestBuilder](RRConfigActionType.name) { - override def newResponse(): RRConfigsResponse = new RRConfigsResponse - - override def newRequestBuilder(client: ElasticsearchClient): RRConfigActionType.RequestBuilder = - new RRConfigActionType.RequestBuilder(client, this, new RRConfigsRequest()) -} - -object RRConfigActionType { - class RequestBuilder(client: ElasticsearchClient, rRConfigAction: RRConfigActionType, request: RRConfigsRequest) - extends ActionRequestBuilder[RRConfigsRequest, RRConfigsResponse, RequestBuilder](client, rRConfigAction, request) - - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = RRConfigsResponseReader -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala deleted file mode 100644 index 03e7017e59..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} - -object RRConfigReader extends Writeable.Reader[RRConfig] { - override def read(in: StreamInput): RRConfig = { - val response = new RRConfig - response.readFrom(in) - response - } -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 4cc4529252..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(String nodeId, NodeConfigRequest nodeConfigRequest) { - super(nodeId); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest() { - super(); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 9a7946c917..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private NodeConfigRequest nodeConfigRequest; - - public RRConfigsRequest() { - super(); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 812d77e4ca..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse() { - super(); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfigReader::read); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } - -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala deleted file mode 100644 index 226e15cefe..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} - -object RRConfigsResponseReader extends Writeable.Reader[RRConfigsResponse] { - override def read(in: StreamInput): RRConfigsResponse = { - val response = new RRConfigsResponse - response.readNodesFrom(in) - response - } -} \ No newline at end of file diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index e4f65142ba..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,124 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(setting: Settings, - actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeExecutor: String, - indexNameExpressionResolver: IndexNameExpressionResolver, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - setting, - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - indexNameExpressionResolver, - () => new RRConfigsRequest(), - () => new RRConfigRequest(), - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(setting: Settings, - actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - indexNameExpressionResolver: IndexNameExpressionResolver, - ) = - this( - setting, - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - ThreadPool.Names.GENERIC, - indexNameExpressionResolver, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(): RRConfig = new RRConfig() - - override def newNodeRequest(nodeId: String, request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(nodeId, request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index ecf83b066b..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(settings: Settings, - controller: RestController, - nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler(settings) { - - register("GET", constants.MANAGE_ROR_CONFIG_PATH) - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 1030bdbc81..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{Action, ActionRequestBuilder} -import org.elasticsearch.client.ElasticsearchClient -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends Action[RRTestConfigRequest, RRTestConfigResponse, RRTestConfigActionType.RequestBuilder]( - RRTestConfigActionType.name -) { - - override def newResponse(): RRTestConfigResponse = new RRTestConfigResponse() - - override def newRequestBuilder(client: ElasticsearchClient): RRTestConfigActionType.RequestBuilder = - new RRTestConfigActionType.RequestBuilder(client, this, new RRTestConfigRequest()) -} - -object RRTestConfigActionType { - class RequestBuilder(client: ElasticsearchClient, actionType: RRTestConfigActionType, request: RRTestConfigRequest) - extends ActionRequestBuilder[RRTestConfigRequest, RRTestConfigResponse, RequestBuilder](client, actionType, request) - - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() -} - diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index 095a027ee9..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def this() = { - this(null, null) - } - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3af2f7fc7c..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,133 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - def this() = { - this(null) - } - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 473af7fb41..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(settings: Settings, - threadPool: ThreadPool, - transportService: TransportService, - actionFilters: ActionFilters, - indexNameExpressionResolver: IndexNameExpressionResolver, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - settings, RRTestConfigActionType.name, threadPool, transportService, actionFilters, indexNameExpressionResolver, () => new RRTestConfigRequest() - ) { - - @Inject - def this(settings: Settings, - threadPool: ThreadPool, - transportService: TransportService, - indexNameExpressionResolver: IndexNameExpressionResolver, - actionFilters: ActionFilters) = { - this(settings, threadPool, transportService, actionFilters, indexNameExpressionResolver, ()) - } - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 4fcf252c1f..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -@Inject -class RestRRTestConfigAction(settings: Settings, controller: RestController) - extends BaseRestHandler(settings) with RestHandler { - - register("GET", constants.PROVIDE_TEST_CONFIG_PATH) - register("POST", constants.UPDATE_TEST_CONFIG_PATH) - register("DELETE", constants.DELETE_TEST_CONFIG_PATH) - register("GET", constants.PROVIDE_LOCAL_USERS_PATH) - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..219f633a33 --- /dev/null +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,39 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{Action, ActionRequestBuilder} +import org.elasticsearch.client.ElasticsearchClient +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends Action[RRTestSettingsRequest, RRTestSettingsResponse, RRTestSettingsActionType.RequestBuilder]( + RRTestSettingsActionType.name +) { + + override def newResponse(): RRTestSettingsResponse = new RRTestSettingsResponse() + + override def newRequestBuilder(client: ElasticsearchClient): RRTestSettingsActionType.RequestBuilder = + new RRTestSettingsActionType.RequestBuilder(client, this, new RRTestSettingsRequest()) +} + +object RRTestSettingsActionType { + class RequestBuilder(client: ElasticsearchClient, actionType: RRTestSettingsActionType, request: RRTestSettingsRequest) + extends ActionRequestBuilder[RRTestSettingsRequest, RRTestSettingsResponse, RequestBuilder](client, actionType, request) + + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() +} \ No newline at end of file diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..3021184f9c --- /dev/null +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,64 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def this() = { + this(null, null) + } + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ac79fb48e1 --- /dev/null +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,133 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + def this() = { + this(null) + } + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..d472bba9a6 --- /dev/null +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,53 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.settings.Settings +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(settings: Settings, + threadPool: ThreadPool, + transportService: TransportService, + actionFilters: ActionFilters, + indexNameExpressionResolver: IndexNameExpressionResolver, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + settings, RRTestSettingsActionType.name, threadPool, transportService, actionFilters, indexNameExpressionResolver, () => new RRTestSettingsRequest() + ) { + + @Inject + def this(settings: Settings, + threadPool: ThreadPool, + transportService: TransportService, + indexNameExpressionResolver: IndexNameExpressionResolver, + actionFilters: ActionFilters) = { + this(settings, threadPool, transportService, actionFilters, indexNameExpressionResolver, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..ccdf13ac89 --- /dev/null +++ b/es67x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,49 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.settings.Settings +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +@Inject +class RestRRTestSettingsAction(settings: Settings, controller: RestController) + extends BaseRestHandler(settings) with RestHandler { + + register("GET", constants.PROVIDE_TEST_SETTINGS_PATH) + register("POST", constants.UPDATE_TEST_SETTINGS_PATH) + register("DELETE", constants.DELETE_TEST_SETTINGS_PATH) + register("GET", constants.PROVIDE_LOCAL_USERS_PATH) + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } + + private def register(method: String, path: String): Unit = + controller.registerHandler(RestRequest.Method.valueOf(method), path, this) +} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es67x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 09112e0537..4749a51bfc 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -48,7 +48,6 @@ class RoleIndexSearcherWrapper(indexService: IndexService) extends IndexSearcher } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es67x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es67x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es67x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es67x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..9b4400d45e --- /dev/null +++ b/es67x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,119 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setType("settings") + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es67x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index a6c3824699..0000000000 --- a/es67x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,123 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setType("settings") - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es67x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es67x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index def1155990..4fecea0cd1 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -26,7 +26,8 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.threadpool.ThreadPool -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,12 +36,13 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, - fipsCompliant: Boolean) + ssl: ExternalSslSettings) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es67x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es67x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 3ac42afd15..a4fec091cb 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,13 +43,12 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - fipsCompliant: Boolean) + ssl: InternodeSslSettings) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -65,7 +66,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es67x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es67x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es67x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es67x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es70x/build.gradle b/es70x/build.gradle index e4d5d299d0..4e7b7af4f7 100644 --- a/es70x/build.gradle +++ b/es70x/build.gradle @@ -94,7 +94,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es70x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es70x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 6678bb7ee0..b791d55d2d 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -27,6 +27,7 @@ import org.elasticsearch.snapshots.SnapshotsService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, IndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -34,12 +35,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo} import tech.beshu.ror.implicits.* @@ -58,20 +59,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], snapshotsServiceSupplier: Supplier[Option[SnapshotsService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -213,7 +214,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es70x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es70x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index fa8ce80730..51d170eff0 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.MapperService import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService import org.elasticsearch.threadpool.ThreadPool @@ -49,25 +50,24 @@ import org.elasticsearch.transport.netty4.Netty4Utils import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,19 +96,19 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -128,7 +128,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, SnapshotsServiceInterceptor.snapshotsServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,16 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) xContentRegistry: NamedXContentRegistry, networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = doPrivileged { - new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl) } - ) + } .toMap .asJava } @@ -186,16 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = doPrivileged { - new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl) } - ) + } .toMap .asJava } @@ -208,8 +202,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -229,8 +222,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(settings, restController), new RestRRAuthMockAction(settings, restController), - new RestRRTestConfigAction(settings, restController), - new RestRRConfigAction(settings, restController, nodesInCluster), + new RestRRTestSettingsAction(settings, restController), new RestRRUserMetadataAction(settings, restController), new RestRRAuditEventAction(settings, restController) ).asJava diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index fae036b440..b8c818b3b2 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,6 +23,6 @@ class RRAdminActionType extends Action[RRAdminResponse](RRAdminActionType.name) override def newResponse(): RRAdminResponse = new RRAdminResponse } object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() } \ No newline at end of file diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index 4d3619d24f..e1486b99b6 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") def this() = { @@ -42,19 +42,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7e32e42c62..cb8e25ab99 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -19,11 +19,11 @@ package tech.beshu.ror.es.actions.rradmin import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { def this() = { @@ -32,24 +32,24 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index b049809692..18b5e7ffa1 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.es.actions.rradmin.rest import org.elasticsearch.client.node.NodeClient import org.elasticsearch.common.inject.Inject import org.elasticsearch.common.settings.Settings -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import tech.beshu.ror.constants import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, RRAdminRequest, RRAdminResponse} import tech.beshu.ror.es.utils.RestToXContentWithStatusListener @@ -29,10 +29,10 @@ import tech.beshu.ror.es.utils.RestToXContentWithStatusListener class RestRRAdminAction(settings: Settings, controller: RestController) extends BaseRestHandler(settings) with RestHandler { - register("POST", constants.FORCE_RELOAD_CONFIG_PATH) - register("GET", constants.PROVIDE_INDEX_CONFIG_PATH) - register("POST", constants.UPDATE_INDEX_CONFIG_PATH) - register("GET", constants.PROVIDE_FILE_CONFIG_PATH) + register("POST", constants.FORCE_RELOAD_SETTINGS_PATH) + register("GET", constants.PROVIDE_INDEX_SETTINGS_PATH) + register("POST", constants.UPDATE_INDEX_SETTINGS_PATH) + register("GET", constants.PROVIDE_FILE_SETTINGS_PATH) override val getName: String = "ror-admin-handler" diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index c0257cc82c..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private NodeConfig nodeConfig; - - public RRConfig() { - super(); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index d9b3f9a332..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.Action -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends Action[RRConfigsResponse](RRConfigActionType.name) { - override def newResponse(): RRConfigsResponse = new RRConfigsResponse -} - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = RRConfigsResponseReader -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala deleted file mode 100644 index 03e7017e59..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} - -object RRConfigReader extends Writeable.Reader[RRConfig] { - override def read(in: StreamInput): RRConfig = { - val response = new RRConfig - response.readFrom(in) - response - } -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 4cc4529252..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(String nodeId, NodeConfigRequest nodeConfigRequest) { - super(nodeId); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest() { - super(); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 9a7946c917..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private NodeConfigRequest nodeConfigRequest; - - public RRConfigsRequest() { - super(); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 812d77e4ca..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse() { - super(); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfigReader::read); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } - -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala deleted file mode 100644 index 226e15cefe..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} - -object RRConfigsResponseReader extends Writeable.Reader[RRConfigsResponse] { - override def read(in: StreamInput): RRConfigsResponse = { - val response = new RRConfigsResponse - response.readNodesFrom(in) - response - } -} \ No newline at end of file diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 261715f8bb..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - () => new RRConfigsRequest(), - () => new RRConfigRequest(), - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(nodeId: String, request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(nodeId, request.getNodeConfigRequest) - - override def newNodeResponse(): RRConfig = new RRConfig() - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index ecf83b066b..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(settings: Settings, - controller: RestController, - nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler(settings) { - - register("GET", constants.MANAGE_ROR_CONFIG_PATH) - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 1de3a2d3e6..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.Action -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends Action[RRTestConfigResponse](RRTestConfigActionType.name) { - override def newResponse(): RRTestConfigResponse = - new RRTestConfigResponse() -} - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index 095a027ee9..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def this() = { - this(null, null) - } - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3af2f7fc7c..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,133 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - def this() = { - this(null) - } - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 47733a387e..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, () => new RRTestConfigRequest() - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 4fcf252c1f..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -@Inject -class RestRRTestConfigAction(settings: Settings, controller: RestController) - extends BaseRestHandler(settings) with RestHandler { - - register("GET", constants.PROVIDE_TEST_CONFIG_PATH) - register("POST", constants.UPDATE_TEST_CONFIG_PATH) - register("DELETE", constants.DELETE_TEST_CONFIG_PATH) - register("GET", constants.PROVIDE_LOCAL_USERS_PATH) - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..24b98902d8 --- /dev/null +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,30 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.Action +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends Action[RRTestSettingsResponse](RRTestSettingsActionType.name) { + override def newResponse(): RRTestSettingsResponse = + new RRTestSettingsResponse() +} + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() +} \ No newline at end of file diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..3021184f9c --- /dev/null +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,64 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def this() = { + this(null, null) + } + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ac79fb48e1 --- /dev/null +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,133 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + def this() = { + this(null) + } + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..748b3b944a --- /dev/null +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, () => new RRTestSettingsRequest() + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..ccdf13ac89 --- /dev/null +++ b/es70x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,49 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.settings.Settings +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +@Inject +class RestRRTestSettingsAction(settings: Settings, controller: RestController) + extends BaseRestHandler(settings) with RestHandler { + + register("GET", constants.PROVIDE_TEST_SETTINGS_PATH) + register("POST", constants.UPDATE_TEST_SETTINGS_PATH) + register("DELETE", constants.DELETE_TEST_SETTINGS_PATH) + register("GET", constants.PROVIDE_LOCAL_USERS_PATH) + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } + + private def register(method: String, path: String): Unit = + controller.registerHandler(RestRequest.Method.valueOf(method), path, this) +} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es70x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 09112e0537..4749a51bfc 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -48,7 +48,6 @@ class RoleIndexSearcherWrapper(indexService: IndexService) extends IndexSearcher } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es70x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es70x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es70x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es70x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es70x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es70x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es70x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es70x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es70x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index f3b70cb4ae..bae2f793b0 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -26,7 +26,8 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,12 +36,13 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, - fipsCompliant: Boolean) + ssl: ExternalSslSettings) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es70x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es70x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 3ac42afd15..a4fec091cb 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,13 +43,12 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - fipsCompliant: Boolean) + ssl: InternodeSslSettings) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -65,7 +66,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es70x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es70x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es70x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es70x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es710x/build.gradle b/es710x/build.gradle index a36653dc2d..808d8042b4 100644 --- a/es710x/build.gradle +++ b/es710x/build.gradle @@ -95,7 +95,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es710x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es710x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index dfc3613b7e..1c63128a26 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es710x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es710x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index c183bfa8cc..b92a933cfa 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -50,26 +51,25 @@ import org.elasticsearch.transport.{SharedGroupFactory, Transport, TransportInte import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, RepositoriesServiceInterceptor.repositoriesServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -173,14 +173,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -191,14 +190,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -216,8 +214,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -237,8 +234,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 950e053439..8eaaf10e73 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,33 +20,33 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -57,10 +57,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index b6aea7a594..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - override def newNodeResponse(in: StreamInput): RRConfig = - new RRConfig(in) - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = loadConfig().runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 443de5791a..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 68c81790f3..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..16e8851da7 --- /dev/null +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,129 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es710x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es710x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 5e42d55f7e..6121dabc12 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es710x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es710x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es710x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es710x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es710x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es710x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es710x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es710x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es710x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index a9f97756f7..7c3b1fda94 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es710x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es710x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index cd73e6310c..6919f5755d 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -67,7 +68,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es710x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es710x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es710x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es710x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es711x/build.gradle b/es711x/build.gradle index aacb91dd03..65eb04f442 100644 --- a/es711x/build.gradle +++ b/es711x/build.gradle @@ -97,7 +97,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es711x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es711x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index dfc3613b7e..1c63128a26 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es711x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es711x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index c183bfa8cc..b92a933cfa 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -50,26 +51,25 @@ import org.elasticsearch.transport.{SharedGroupFactory, Transport, TransportInte import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, RepositoriesServiceInterceptor.repositoriesServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -173,14 +173,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -191,14 +190,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -216,8 +214,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -237,8 +234,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 950e053439..8eaaf10e73 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,33 +20,33 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -57,10 +57,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index b6aea7a594..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - override def newNodeResponse(in: StreamInput): RRConfig = - new RRConfig(in) - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = loadConfig().runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 443de5791a..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 68c81790f3..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..16e8851da7 --- /dev/null +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,129 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es711x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es711x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 5e42d55f7e..6121dabc12 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es711x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es711x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es711x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es711x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es711x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es711x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es711x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es711x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es711x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index a9f97756f7..7c3b1fda94 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es711x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es711x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index cd73e6310c..6919f5755d 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -67,7 +68,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es711x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es711x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es711x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es711x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es714x/build.gradle b/es714x/build.gradle index 0c6cd800fe..fa6e9952f4 100644 --- a/es714x/build.gradle +++ b/es714x/build.gradle @@ -94,7 +94,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es714x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es714x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index dfc3613b7e..1c63128a26 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es714x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es714x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index c183bfa8cc..b92a933cfa 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -50,26 +51,25 @@ import org.elasticsearch.transport.{SharedGroupFactory, Transport, TransportInte import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, RepositoriesServiceInterceptor.repositoriesServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -173,14 +173,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -191,14 +190,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -216,8 +214,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -237,8 +234,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 950e053439..8eaaf10e73 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,33 +20,33 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -57,10 +57,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index b6aea7a594..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - override def newNodeResponse(in: StreamInput): RRConfig = - new RRConfig(in) - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = loadConfig().runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 995c7e8bce..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 68c81790f3..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..16e8851da7 --- /dev/null +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,129 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es714x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es714x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es714x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es714x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es714x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es714x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es714x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es714x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es714x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es714x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es714x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index a9f97756f7..7c3b1fda94 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es714x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es714x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index cd73e6310c..6919f5755d 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -67,7 +68,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es714x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es714x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es714x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es714x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es716x/build.gradle b/es716x/build.gradle index d0c9fbe623..dfe16e041c 100644 --- a/es716x/build.gradle +++ b/es716x/build.gradle @@ -95,7 +95,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es716x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es716x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index c1eb97ae22..d3f1c12ba2 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es716x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es716x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index a60e1a1943..ed82d9aa2b 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -38,8 +39,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -50,26 +51,25 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, RepositoriesServiceInterceptor.repositoriesServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -173,14 +173,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -191,14 +190,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -216,8 +214,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -237,8 +234,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 1da6cd216f..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,124 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 995c7e8bce..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index d0bd030271..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es716x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es716x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es716x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es716x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es716x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es716x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..71f6ba880d --- /dev/null +++ b/es716x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es716x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2a9a3dfd8f..0000000000 --- a/es716x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es716x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es716x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index a0a7d92ae4..58d8c8c639 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es716x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es716x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index cd73e6310c..6919f5755d 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -67,7 +68,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es716x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es716x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es716x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es716x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es717x/build.gradle b/es717x/build.gradle index d0c9fbe623..dfe16e041c 100644 --- a/es717x/build.gradle +++ b/es717x/build.gradle @@ -95,7 +95,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es717x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es717x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index c1eb97ae22..d3f1c12ba2 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es717x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es717x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 11770476cd..95ed0bf071 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -38,8 +39,8 @@ import org.elasticsearch.http.{HttpPreRequest, HttpServerTransport} import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -50,26 +51,25 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, RepositoriesServiceInterceptor.repositoriesServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -174,14 +174,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) dispatcher: HttpServerTransport.Dispatcher, perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -192,14 +191,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -217,8 +215,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -238,8 +235,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 1da6cd216f..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,124 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 995c7e8bce..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index d0bd030271..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es717x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es717x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es717x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es717x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es717x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es717x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..71f6ba880d --- /dev/null +++ b/es717x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es717x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2a9a3dfd8f..0000000000 --- a/es717x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es717x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es717x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 7344bc6c4f..1af576d24f 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es717x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es717x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index cd73e6310c..6919f5755d 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -67,7 +68,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es717x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es717x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es717x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es717x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es72x/build.gradle b/es72x/build.gradle index e4d5d299d0..4e7b7af4f7 100644 --- a/es72x/build.gradle +++ b/es72x/build.gradle @@ -94,7 +94,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es72x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es72x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 6678bb7ee0..b791d55d2d 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -27,6 +27,7 @@ import org.elasticsearch.snapshots.SnapshotsService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, IndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -34,12 +35,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo} import tech.beshu.ror.implicits.* @@ -58,20 +59,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], snapshotsServiceSupplier: Supplier[Option[SnapshotsService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -213,7 +214,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es72x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es72x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index fa8ce80730..51d170eff0 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.MapperService import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService import org.elasticsearch.threadpool.ThreadPool @@ -49,25 +50,24 @@ import org.elasticsearch.transport.netty4.Netty4Utils import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,19 +96,19 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -128,7 +128,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, SnapshotsServiceInterceptor.snapshotsServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,16 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) xContentRegistry: NamedXContentRegistry, networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = doPrivileged { - new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl) } - ) + } .toMap .asJava } @@ -186,16 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = doPrivileged { - new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl) } - ) + } .toMap .asJava } @@ -208,8 +202,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -229,8 +222,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(settings, restController), new RestRRAuthMockAction(settings, restController), - new RestRRTestConfigAction(settings, restController), - new RestRRConfigAction(settings, restController, nodesInCluster), + new RestRRTestSettingsAction(settings, restController), new RestRRUserMetadataAction(settings, restController), new RestRRAuditEventAction(settings, restController) ).asJava diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index fae036b440..b8c818b3b2 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,6 +23,6 @@ class RRAdminActionType extends Action[RRAdminResponse](RRAdminActionType.name) override def newResponse(): RRAdminResponse = new RRAdminResponse } object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() } \ No newline at end of file diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index 4d3619d24f..e1486b99b6 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") def this() = { @@ -42,19 +42,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7e32e42c62..cb8e25ab99 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -19,11 +19,11 @@ package tech.beshu.ror.es.actions.rradmin import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { def this() = { @@ -32,24 +32,24 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index b049809692..18b5e7ffa1 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.es.actions.rradmin.rest import org.elasticsearch.client.node.NodeClient import org.elasticsearch.common.inject.Inject import org.elasticsearch.common.settings.Settings -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import tech.beshu.ror.constants import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, RRAdminRequest, RRAdminResponse} import tech.beshu.ror.es.utils.RestToXContentWithStatusListener @@ -29,10 +29,10 @@ import tech.beshu.ror.es.utils.RestToXContentWithStatusListener class RestRRAdminAction(settings: Settings, controller: RestController) extends BaseRestHandler(settings) with RestHandler { - register("POST", constants.FORCE_RELOAD_CONFIG_PATH) - register("GET", constants.PROVIDE_INDEX_CONFIG_PATH) - register("POST", constants.UPDATE_INDEX_CONFIG_PATH) - register("GET", constants.PROVIDE_FILE_CONFIG_PATH) + register("POST", constants.FORCE_RELOAD_SETTINGS_PATH) + register("GET", constants.PROVIDE_INDEX_SETTINGS_PATH) + register("POST", constants.UPDATE_INDEX_SETTINGS_PATH) + register("GET", constants.PROVIDE_FILE_SETTINGS_PATH) override val getName: String = "ror-admin-handler" diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index c0257cc82c..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private NodeConfig nodeConfig; - - public RRConfig() { - super(); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index d9b3f9a332..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,31 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.Action -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends Action[RRConfigsResponse](RRConfigActionType.name) { - override def newResponse(): RRConfigsResponse = new RRConfigsResponse -} - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = RRConfigsResponseReader -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala deleted file mode 100644 index 03e7017e59..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} - -object RRConfigReader extends Writeable.Reader[RRConfig] { - override def read(in: StreamInput): RRConfig = { - val response = new RRConfig - response.readFrom(in) - response - } -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 4cc4529252..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(String nodeId, NodeConfigRequest nodeConfigRequest) { - super(nodeId); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest() { - super(); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 9a7946c917..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private NodeConfigRequest nodeConfigRequest; - - public RRConfigsRequest() { - super(); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 812d77e4ca..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse() { - super(); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfigReader::read); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } - -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala deleted file mode 100644 index 226e15cefe..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} - -object RRConfigsResponseReader extends Writeable.Reader[RRConfigsResponse] { - override def read(in: StreamInput): RRConfigsResponse = { - val response = new RRConfigsResponse - response.readNodesFrom(in) - response - } -} \ No newline at end of file diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 261715f8bb..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - () => new RRConfigsRequest(), - () => new RRConfigRequest(), - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(nodeId: String, request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(nodeId, request.getNodeConfigRequest) - - override def newNodeResponse(): RRConfig = new RRConfig() - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index ecf83b066b..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(settings: Settings, - controller: RestController, - nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler(settings) { - - register("GET", constants.MANAGE_ROR_CONFIG_PATH) - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 1de3a2d3e6..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.Action -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends Action[RRTestConfigResponse](RRTestConfigActionType.name) { - override def newResponse(): RRTestConfigResponse = - new RRTestConfigResponse() -} - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index 095a027ee9..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def this() = { - this(null, null) - } - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3af2f7fc7c..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,133 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - def this() = { - this(null) - } - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 47733a387e..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, () => new RRTestConfigRequest() - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 4fcf252c1f..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -@Inject -class RestRRTestConfigAction(settings: Settings, controller: RestController) - extends BaseRestHandler(settings) with RestHandler { - - register("GET", constants.PROVIDE_TEST_CONFIG_PATH) - register("POST", constants.UPDATE_TEST_CONFIG_PATH) - register("DELETE", constants.DELETE_TEST_CONFIG_PATH) - register("GET", constants.PROVIDE_LOCAL_USERS_PATH) - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..24b98902d8 --- /dev/null +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,30 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.Action +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends Action[RRTestSettingsResponse](RRTestSettingsActionType.name) { + override def newResponse(): RRTestSettingsResponse = + new RRTestSettingsResponse() +} + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() +} \ No newline at end of file diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..3021184f9c --- /dev/null +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,64 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def this() = { + this(null, null) + } + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ac79fb48e1 --- /dev/null +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,133 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + def this() = { + this(null) + } + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..748b3b944a --- /dev/null +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, () => new RRTestSettingsRequest() + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..ccdf13ac89 --- /dev/null +++ b/es72x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,49 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.settings.Settings +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +@Inject +class RestRRTestSettingsAction(settings: Settings, controller: RestController) + extends BaseRestHandler(settings) with RestHandler { + + register("GET", constants.PROVIDE_TEST_SETTINGS_PATH) + register("POST", constants.UPDATE_TEST_SETTINGS_PATH) + register("DELETE", constants.DELETE_TEST_SETTINGS_PATH) + register("GET", constants.PROVIDE_LOCAL_USERS_PATH) + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } + + private def register(method: String, path: String): Unit = + controller.registerHandler(RestRequest.Method.valueOf(method), path, this) +} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es72x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 09112e0537..4749a51bfc 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -48,7 +48,6 @@ class RoleIndexSearcherWrapper(indexService: IndexService) extends IndexSearcher } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es72x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es72x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es72x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es72x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es72x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es72x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es72x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es72x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es72x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index f3b70cb4ae..bae2f793b0 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -26,7 +26,8 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,12 +36,13 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, - fipsCompliant: Boolean) + ssl: ExternalSslSettings) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es72x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es72x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 3ac42afd15..a4fec091cb 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,13 +43,12 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - fipsCompliant: Boolean) + ssl: InternodeSslSettings) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -65,7 +66,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es72x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es72x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es72x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es72x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es73x/build.gradle b/es73x/build.gradle index e4d5d299d0..4e7b7af4f7 100644 --- a/es73x/build.gradle +++ b/es73x/build.gradle @@ -94,7 +94,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es73x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es73x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 6678bb7ee0..b791d55d2d 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -27,6 +27,7 @@ import org.elasticsearch.snapshots.SnapshotsService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, IndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -34,12 +35,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo} import tech.beshu.ror.implicits.* @@ -58,20 +59,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], snapshotsServiceSupplier: Supplier[Option[SnapshotsService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -213,7 +214,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es73x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es73x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 9cf7f3301e..5d63c3a201 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.MapperService import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService import org.elasticsearch.threadpool.ThreadPool @@ -49,25 +50,24 @@ import org.elasticsearch.transport.netty4.Netty4Utils import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,19 +96,19 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -128,7 +128,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, SnapshotsServiceInterceptor.snapshotsServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,16 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) xContentRegistry: NamedXContentRegistry, networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = doPrivileged { - new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl) } - ) + } .toMap .asJava } @@ -186,16 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = doPrivileged { - new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl) } - ) + } .toMap .asJava } @@ -208,8 +202,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -229,8 +222,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(settings, restController), new RestRRAuthMockAction(settings, restController), - new RestRRTestConfigAction(settings, restController), - new RestRRConfigAction(settings, restController, nodesInCluster), + new RestRRTestSettingsAction(settings, restController), new RestRRUserMetadataAction(settings, restController), new RestRRAuditEventAction(settings, restController) ).asJava diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index 4d3619d24f..e1486b99b6 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") def this() = { @@ -42,19 +42,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index d36f58a813..7edb7a7c90 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -19,33 +19,33 @@ package tech.beshu.ror.es.actions.rradmin import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -54,10 +54,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index b049809692..18b5e7ffa1 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -19,8 +19,8 @@ package tech.beshu.ror.es.actions.rradmin.rest import org.elasticsearch.client.node.NodeClient import org.elasticsearch.common.inject.Inject import org.elasticsearch.common.settings.Settings -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer import tech.beshu.ror.constants import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, RRAdminRequest, RRAdminResponse} import tech.beshu.ror.es.utils.RestToXContentWithStatusListener @@ -29,10 +29,10 @@ import tech.beshu.ror.es.utils.RestToXContentWithStatusListener class RestRRAdminAction(settings: Settings, controller: RestController) extends BaseRestHandler(settings) with RestHandler { - register("POST", constants.FORCE_RELOAD_CONFIG_PATH) - register("GET", constants.PROVIDE_INDEX_CONFIG_PATH) - register("POST", constants.UPDATE_INDEX_CONFIG_PATH) - register("GET", constants.PROVIDE_FILE_CONFIG_PATH) + register("POST", constants.FORCE_RELOAD_SETTINGS_PATH) + register("GET", constants.PROVIDE_INDEX_SETTINGS_PATH) + register("POST", constants.UPDATE_INDEX_SETTINGS_PATH) + register("GET", constants.PROVIDE_FILE_SETTINGS_PATH) override val getName: String = "ror-admin-handler" diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index c0257cc82c..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private NodeConfig nodeConfig; - - public RRConfig() { - super(); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index e59b752080..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = RRConfigsResponseReader -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala deleted file mode 100644 index 03e7017e59..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigReader.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} - -object RRConfigReader extends Writeable.Reader[RRConfig] { - override def read(in: StreamInput): RRConfig = { - val response = new RRConfig - response.readFrom(in) - response - } -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index d8999cdc37..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest() { - super(); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 9a7946c917..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private NodeConfigRequest nodeConfigRequest; - - public RRConfigsRequest() { - super(); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 812d77e4ca..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse() { - super(); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfigReader::read); - } - - @Override - public void readFrom(StreamInput in) throws IOException { - super.readFrom(in); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } - -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala deleted file mode 100644 index 226e15cefe..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponseReader.scala +++ /dev/null @@ -1,27 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} - -object RRConfigsResponseReader extends Writeable.Reader[RRConfigsResponse] { - override def read(in: StreamInput): RRConfigsResponse = { - val response = new RRConfigsResponse - response.readNodesFrom(in) - response - } -} \ No newline at end of file diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index bf4f9a51a1..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,115 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - () => new RRConfigsRequest(), - () => new RRConfigRequest(), - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - override def newNodeResponse(): RRConfig = - new RRConfig() - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index ecf83b066b..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(settings: Settings, - controller: RestController, - nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler(settings) { - - register("GET", constants.MANAGE_ROR_CONFIG_PATH) - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index 095a027ee9..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def this() = { - this(null, null) - } - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3af2f7fc7c..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,133 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - def this() = { - this(null) - } - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 47733a387e..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, () => new RRTestConfigRequest() - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 4fcf252c1f..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.settings.Settings -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -@Inject -class RestRRTestConfigAction(settings: Settings, controller: RestController) - extends BaseRestHandler(settings) with RestHandler { - - register("GET", constants.PROVIDE_TEST_CONFIG_PATH) - register("POST", constants.UPDATE_TEST_CONFIG_PATH) - register("DELETE", constants.DELETE_TEST_CONFIG_PATH) - register("GET", constants.PROVIDE_LOCAL_USERS_PATH) - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..16e8851da7 --- /dev/null +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,129 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..ccdf13ac89 --- /dev/null +++ b/es73x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,49 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.settings.Settings +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +@Inject +class RestRRTestSettingsAction(settings: Settings, controller: RestController) + extends BaseRestHandler(settings) with RestHandler { + + register("GET", constants.PROVIDE_TEST_SETTINGS_PATH) + register("POST", constants.UPDATE_TEST_SETTINGS_PATH) + register("DELETE", constants.DELETE_TEST_SETTINGS_PATH) + register("GET", constants.PROVIDE_LOCAL_USERS_PATH) + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } + + private def register(method: String, path: String): Unit = + controller.registerHandler(RestRequest.Method.valueOf(method), path, this) +} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es73x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 5e42d55f7e..6121dabc12 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es73x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es73x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es73x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es73x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es73x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es73x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es73x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es73x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es73x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index f3b70cb4ae..bae2f793b0 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -26,7 +26,8 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,12 +36,13 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, - fipsCompliant: Boolean) + ssl: ExternalSslSettings) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es73x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es73x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 3ac42afd15..a4fec091cb 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,13 +43,12 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - fipsCompliant: Boolean) + ssl: InternodeSslSettings) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -65,7 +66,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es73x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es73x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es73x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es73x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es74x/build.gradle b/es74x/build.gradle index 3c7f162515..5fd35ee10c 100644 --- a/es74x/build.gradle +++ b/es74x/build.gradle @@ -102,7 +102,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es74x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es74x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 6678bb7ee0..b791d55d2d 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -27,6 +27,7 @@ import org.elasticsearch.snapshots.SnapshotsService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, IndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -34,12 +35,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo} import tech.beshu.ror.implicits.* @@ -58,20 +59,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], snapshotsServiceSupplier: Supplier[Option[SnapshotsService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -213,7 +214,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es74x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es74x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 1222869380..7962bb140e 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.MapperService import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService import org.elasticsearch.threadpool.ThreadPool @@ -49,25 +50,24 @@ import org.elasticsearch.transport.netty4.Netty4Utils import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,19 +96,19 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -128,7 +128,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, SnapshotsServiceInterceptor.snapshotsServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,16 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) xContentRegistry: NamedXContentRegistry, networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = doPrivileged { - new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl) } - ) + } .toMap .asJava } @@ -186,16 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = doPrivileged { - new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl) } - ) + } .toMap .asJava } @@ -208,8 +202,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -229,8 +222,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(restController), new RestRRAuthMockAction(restController), - new RestRRTestConfigAction(restController), - new RestRRConfigAction(restController, nodesInCluster), + new RestRRTestSettingsAction(restController), new RestRRUserMetadataAction(restController), new RestRRAuditEventAction(restController) ).asJava diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 950e053439..8eaaf10e73 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,33 +20,33 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -57,10 +57,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 89433a61d9..6cba50142b 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -28,10 +28,10 @@ import tech.beshu.ror.es.utils.RestToXContentWithStatusListener class RestRRAdminAction(controller: RestController) extends BaseRestHandler with RestHandler { - register("POST", constants.FORCE_RELOAD_CONFIG_PATH) - register("GET", constants.PROVIDE_INDEX_CONFIG_PATH) - register("POST", constants.UPDATE_INDEX_CONFIG_PATH) - register("GET", constants.PROVIDE_FILE_CONFIG_PATH) + register("POST", constants.FORCE_RELOAD_SETTINGS_PATH) + register("GET", constants.PROVIDE_FILE_SETTINGS_PATH) + register("GET", constants.PROVIDE_INDEX_SETTINGS_PATH) + register("POST", constants.UPDATE_INDEX_SETTINGS_PATH) override val getName: String = "ror-admin-handler" @@ -46,4 +46,4 @@ class RestRRAdminAction(controller: RestController) private def register(method: String, path: String): Unit = { controller.registerHandler(RestRequest.Method.valueOf(method), path, this) } -} \ No newline at end of file +} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 88b7eadc0e..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.{EsEnv, IndexJsonContentService} -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - override def newNodeResponse(in: StreamInput): RRConfig = - new RRConfig(in) - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = loadConfig().runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 6b112f0e0e..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(controller: RestController, - nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - register("GET", constants.MANAGE_ROR_CONFIG_PATH) - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 68c81790f3..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 9bf45abb4e..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -@Inject -class RestRRTestConfigAction(controller: RestController) - extends BaseRestHandler with RestHandler { - - register("GET", constants.PROVIDE_TEST_CONFIG_PATH) - register("POST", constants.UPDATE_TEST_CONFIG_PATH) - register("DELETE", constants.DELETE_TEST_CONFIG_PATH) - register("GET", constants.PROVIDE_LOCAL_USERS_PATH) - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } - - private def register(method: String, path: String): Unit = - controller.registerHandler(RestRequest.Method.valueOf(method), path, this) -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..16e8851da7 --- /dev/null +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,129 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..664214265b --- /dev/null +++ b/es74x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +@Inject +class RestRRTestSettingsAction(controller: RestController) + extends BaseRestHandler with RestHandler { + + register("GET", constants.PROVIDE_TEST_SETTINGS_PATH) + register("POST", constants.UPDATE_TEST_SETTINGS_PATH) + register("DELETE", constants.DELETE_TEST_SETTINGS_PATH) + register("GET", constants.PROVIDE_LOCAL_USERS_PATH) + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } + + private def register(method: String, path: String): Unit = + controller.registerHandler(RestRequest.Method.valueOf(method), path, this) +} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es74x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 5e42d55f7e..6121dabc12 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es74x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es74x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es74x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es74x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es74x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es74x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es74x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es74x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es74x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index f3b70cb4ae..bae2f793b0 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -26,7 +26,8 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,12 +36,13 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, - fipsCompliant: Boolean) + ssl: ExternalSslSettings) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es74x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es74x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 3ac42afd15..a4fec091cb 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,13 +43,12 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - fipsCompliant: Boolean) + ssl: InternodeSslSettings) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -65,7 +66,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es74x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es74x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es74x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es74x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es77x/build.gradle b/es77x/build.gradle index 1f117b851d..b6dd048b4d 100644 --- a/es77x/build.gradle +++ b/es77x/build.gradle @@ -95,7 +95,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es77x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es77x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 95276bc313..00752386cc 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -27,6 +27,7 @@ import org.elasticsearch.snapshots.SnapshotsService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, IndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -34,12 +35,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo} import tech.beshu.ror.implicits.* @@ -58,20 +59,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], snapshotsServiceSupplier: Supplier[Option[SnapshotsService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -212,7 +213,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es77x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es77x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 13f361be70..d68bd2be24 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.MapperService import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService import org.elasticsearch.threadpool.ThreadPool @@ -49,25 +50,24 @@ import org.elasticsearch.transport.netty4.Netty4Utils import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,19 +96,19 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -129,7 +129,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, SnapshotsServiceInterceptor.snapshotsServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -168,16 +168,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = doPrivileged { - new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings) } - ) + } .toMap .asJava } @@ -188,16 +185,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = doPrivileged { - new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl) } - ) + } .toMap .asJava } @@ -210,8 +204,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -231,8 +224,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 950e053439..8eaaf10e73 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,33 +20,33 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -57,10 +57,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index b6aea7a594..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - override def newNodeResponse(in: StreamInput): RRConfig = - new RRConfig(in) - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = loadConfig().runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 443de5791a..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 68c81790f3..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..16e8851da7 --- /dev/null +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,129 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es77x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es77x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 5e42d55f7e..6121dabc12 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es77x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es77x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es77x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es77x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es77x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es77x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es77x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es77x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es77x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 1304142499..606c0f4c18 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -26,7 +26,8 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,13 +36,14 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, - clusterSettings: ClusterSettings, - fipsCompliant: Boolean) + ssl: ExternalSslSettings, + clusterSettings: ClusterSettings) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es77x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es77x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 3ac42afd15..a4fec091cb 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,13 +43,12 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - fipsCompliant: Boolean) + ssl: InternodeSslSettings) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -65,7 +66,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es77x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es77x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es77x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es77x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es78x/build.gradle b/es78x/build.gradle index a36653dc2d..808d8042b4 100644 --- a/es78x/build.gradle +++ b/es78x/build.gradle @@ -95,7 +95,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es78x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es78x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 741142f834..015f4f4905 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -27,6 +27,7 @@ import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, IndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -34,12 +35,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo} import tech.beshu.ror.implicits.* @@ -58,20 +59,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -212,7 +213,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es78x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es78x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 73b4f50400..0c4dcdac1a 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.MapperService import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -50,25 +51,24 @@ import org.elasticsearch.transport.netty4.Netty4Utils import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -97,19 +97,19 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -131,7 +131,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, RepositoriesServiceInterceptor.repositoriesServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -170,16 +170,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = doPrivileged { - new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings) } - ) + } .toMap .asJava } @@ -190,16 +187,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = doPrivileged { - new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl) } - ) + } .toMap .asJava } @@ -212,8 +206,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -233,8 +226,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 950e053439..8eaaf10e73 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,33 +20,33 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -57,10 +57,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index b6aea7a594..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - override def newNodeResponse(in: StreamInput): RRConfig = - new RRConfig(in) - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = loadConfig().runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 443de5791a..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 68c81790f3..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..16e8851da7 --- /dev/null +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,129 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es78x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es78x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 5e42d55f7e..6121dabc12 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es78x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es78x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es78x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es78x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es78x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es78x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es78x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es78x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es78x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 1304142499..606c0f4c18 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -26,7 +26,8 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,13 +36,14 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, - clusterSettings: ClusterSettings, - fipsCompliant: Boolean) + ssl: ExternalSslSettings, + clusterSettings: ClusterSettings) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es78x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es78x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 3ac42afd15..a4fec091cb 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,13 +43,12 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - fipsCompliant: Boolean) + ssl: InternodeSslSettings) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -65,7 +66,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es78x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es78x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es78x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es78x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es79x/build.gradle b/es79x/build.gradle index a36653dc2d..808d8042b4 100644 --- a/es79x/build.gradle +++ b/es79x/build.gradle @@ -95,7 +95,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es79x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es79x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index dfc3613b7e..1c63128a26 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es79x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es79x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 8ec043bc16..b92a933cfa 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -39,8 +40,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -50,26 +51,25 @@ import org.elasticsearch.transport.{SharedGroupFactory, Transport, TransportInte import org.elasticsearch.watcher.ResourceWatcherService import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) TransportServiceInterceptor.remoteClusterServiceSupplier, RepositoriesServiceInterceptor.repositoriesServiceSupplier, esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -173,16 +173,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = doPrivileged { - new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -193,16 +190,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = doPrivileged { - new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) - } + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -220,8 +214,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -241,8 +234,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 950e053439..8eaaf10e73 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,33 +20,33 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -57,10 +57,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 5abe81e5d6..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 39a24eff61..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeRequest; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends BaseNodeRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index b6aea7a594..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - override def newNodeResponse(in: StreamInput): RRConfig = - new RRConfig(in) - - override def nodeOperation(request: RRConfigRequest): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = loadConfig().runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 443de5791a..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.unit.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5b78aca31a..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.common.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 68c81790f3..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} -import org.elasticsearch.rest.RestStatus -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..16e8851da7 --- /dev/null +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,129 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.{StatusToXContentObject, ToXContent, XContentBuilder} +import org.elasticsearch.rest.RestStatus +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es79x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es79x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 5e42d55f7e..6121dabc12 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es79x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es79x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es79x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es79x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..e7f5ac4866 --- /dev/null +++ b/es79x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.common.xcontent.XContentType +import org.elasticsearch.index.IndexNotFoundException +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es79x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 18490a232c..0000000000 --- a/es79x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.common.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es79x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es79x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index a9f97756f7..7c3b1fda94 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.netty4.Netty4HttpServerTransport import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es79x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es79x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index cd73e6310c..6919f5755d 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.SharedGroupFactory import org.elasticsearch.transport.netty4.Netty4Transport -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -67,7 +68,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es79x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es79x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es79x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es79x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es80x/build.gradle b/es80x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es80x/build.gradle +++ b/es80x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es80x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es80x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index c1eb97ae22..d3f1c12ba2 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es80x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es80x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index b29555233a..de707dbc39 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -37,8 +38,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -49,27 +50,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,14 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -184,14 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -209,8 +207,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -231,8 +228,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index fca63703b9..11c1034928 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 552456d44d..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 995c7e8bce..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index d0bd030271..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 74c96fdf67..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..26e9e92cb0 --- /dev/null +++ b/es80x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.rest.* +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es80x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es80x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es80x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es80x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es80x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..71f6ba880d --- /dev/null +++ b/es80x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es80x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2a9a3dfd8f..0000000000 --- a/es80x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es80x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es80x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 946cfa4c28..c40f2af9e6 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.SharedGroupFactory import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es80x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es80x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index f9f71f30b1..e78670311e 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,14 +43,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -66,7 +67,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es80x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es80x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es80x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es80x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es810x/build.gradle b/es810x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es810x/build.gradle +++ b/es810x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es810x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es810x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es810x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es810x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index c4d03c11b9..992492c525 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -52,27 +53,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -103,20 +103,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -142,7 +142,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -176,14 +176,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -194,14 +193,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -219,8 +217,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -241,8 +238,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 6a08b286cb..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es810x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es810x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es810x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es810x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es810x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es810x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es810x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es810x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es810x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es810x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es810x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 56afacb5bb..ef14b6eb89 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.tracing.Tracer import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es810x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es810x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es810x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es810x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es810x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es810x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es811x/build.gradle b/es811x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es811x/build.gradle +++ b/es811x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es811x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es811x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es811x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es811x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 02840ae20b..98978457ef 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -53,27 +54,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -104,20 +104,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -143,7 +143,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -177,14 +177,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -195,14 +194,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -220,8 +218,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -242,8 +239,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 1357e117f7..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,123 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 5bd6a7e3e6..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..49fb686fe1 --- /dev/null +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es811x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es811x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es811x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es811x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es811x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es811x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es811x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es811x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es811x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es811x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es811x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index ce17639c06..81b7891185 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es811x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es811x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es811x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es811x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es811x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es811x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es812x/build.gradle b/es812x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es812x/build.gradle +++ b/es812x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es812x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es812x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es812x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es812x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 836fb4cc7d..1a98de367a 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -45,27 +46,26 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,20 +96,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -124,7 +124,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -158,14 +158,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -176,14 +175,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -201,8 +199,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -223,8 +220,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 739ccc22bc..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 5bd6a7e3e6..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..49fb686fe1 --- /dev/null +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es812x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es812x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es812x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es812x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es812x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es812x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es812x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es812x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es812x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es812x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es812x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index ce17639c06..81b7891185 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es812x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es812x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es812x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es812x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es812x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es812x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es813x/build.gradle b/es813x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es813x/build.gradle +++ b/es813x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es813x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es813x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es813x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es813x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index d93ddc133b..f79b6ee525 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -46,27 +47,26 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -97,20 +97,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -125,7 +125,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -159,14 +159,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -177,14 +176,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -202,8 +200,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -226,8 +223,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index 12bfaf5f7b..45c9923dc5 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,7 +23,7 @@ import tech.beshu.ror.accesscontrol.domain.Action.RorAction class RRAdminActionType extends ActionType[RRAdminResponse](RRAdminActionType.name) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 942897ce23..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 739ccc22bc..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 35dfdaaa74..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse](RRTestConfigActionType.name) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 5bd6a7e3e6..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..2ed1b97248 --- /dev/null +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,33 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse](RRTestSettingsActionType.name) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRTestSettingsActionCannotBeTransported extends Exception + + private [rrtestsettings] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestSettingsActionCannotBeTransported +} + diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..49fb686fe1 --- /dev/null +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es813x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es813x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es813x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es813x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es813x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es813x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es813x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es813x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es813x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es813x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es813x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index ce17639c06..81b7891185 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es813x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es813x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es813x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es813x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es813x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es813x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es814x/build.gradle b/es814x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es814x/build.gradle +++ b/es814x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es814x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es814x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es814x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es814x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index d93ddc133b..f79b6ee525 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -46,27 +47,26 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -97,20 +97,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -125,7 +125,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -159,14 +159,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -177,14 +176,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -202,8 +200,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -226,8 +223,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index 12bfaf5f7b..45c9923dc5 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,7 +23,7 @@ import tech.beshu.ror.accesscontrol.domain.Action.RorAction class RRAdminActionType extends ActionType[RRAdminResponse](RRAdminActionType.name) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 942897ce23..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 739ccc22bc..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 35dfdaaa74..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse](RRTestConfigActionType.name) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 5bd6a7e3e6..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..2ed1b97248 --- /dev/null +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,33 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse](RRTestSettingsActionType.name) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRTestSettingsActionCannotBeTransported extends Exception + + private [rrtestsettings] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestSettingsActionCannotBeTransported +} + diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..49fb686fe1 --- /dev/null +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es814x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es814x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es814x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es814x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es814x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es814x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es814x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es814x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 5e693123f3..0000000000 --- a/es814x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es814x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es814x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index ce17639c06..81b7891185 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es814x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es814x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es814x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es814x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es814x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es814x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es815x/build.gradle b/es815x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es815x/build.gradle +++ b/es815x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es815x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es815x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es815x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es815x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index e9fbb34088..fb007c80e1 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -47,27 +48,26 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -128,7 +128,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(client), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -162,14 +162,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -180,14 +179,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -205,8 +203,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -229,8 +226,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index 12bfaf5f7b..45c9923dc5 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,7 +23,7 @@ import tech.beshu.ror.accesscontrol.domain.Action.RorAction class RRAdminActionType extends ActionType[RRAdminResponse](RRAdminActionType.name) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 942897ce23..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 4cb1a900b3..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - public RRConfigsRequest(DiscoveryNode... concreteNodes) { - super(concreteNodes); - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 6e47a4e72d..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,118 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest -import tech.beshu.ror.es.utils.EsEnvProvider - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(new NodeConfigRequest(NodeConfigRequest.defaultTimeout)) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a304c06284..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,57 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def nodes = - nodesInCluster.get().asScala.toList - -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 35dfdaaa74..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse](RRTestConfigActionType.name) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 5bd6a7e3e6..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..2ed1b97248 --- /dev/null +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,33 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse](RRTestSettingsActionType.name) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRTestSettingsActionCannotBeTransported extends Exception + + private [rrtestsettings] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestSettingsActionCannotBeTransported +} + diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..49fb686fe1 --- /dev/null +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es815x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es815x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es815x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es815x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es815x/src/main/scala/tech/beshu/ror/es/handler/request/context/types/templates/GetComponentTemplateEsRequestContext.scala b/es815x/src/main/scala/tech/beshu/ror/es/handler/request/context/types/templates/GetComponentTemplateEsRequestContext.scala index d0235fd407..a12ab32d6e 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/handler/request/context/types/templates/GetComponentTemplateEsRequestContext.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/handler/request/context/types/templates/GetComponentTemplateEsRequestContext.scala @@ -91,7 +91,7 @@ class GetComponentTemplateEsRequestContext(actionRequest: GetComponentTemplateAc new GetComponentTemplateAction.Response( filter( templates = r.getComponentTemplates.asSafeMap, - usingTemplate = using.responseTemplateTransformation + usingTemplate = `using`.responseTemplateTransformation ), r.getRolloverConfiguration, r.getGlobalRetention diff --git a/es815x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es815x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es815x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es815x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 5e693123f3..0000000000 --- a/es815x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es815x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es815x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index ce17639c06..81b7891185 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es815x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es815x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es815x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es815x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es815x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es815x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es816x/build.gradle b/es816x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es816x/build.gradle +++ b/es816x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es816x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es816x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es816x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es816x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index b50adedf30..ed16bfcc14 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -47,27 +48,26 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -128,7 +128,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(client), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -162,14 +162,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -180,14 +179,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -205,8 +203,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -229,8 +226,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index 12bfaf5f7b..45c9923dc5 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,7 +23,7 @@ import tech.beshu.ror.accesscontrol.domain.Action.RorAction class RRAdminActionType extends ActionType[RRAdminResponse](RRAdminActionType.name) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 1ecc29436c..7770420170 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -32,10 +32,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 21317a9d83..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.injection.guice.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 942897ce23..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 8e74908a8a..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - public RRConfigsRequest(DiscoveryNode... concreteNodes) { - super(concreteNodes); - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index c0aba7ad0b..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, NodeConfigRequest, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig, Void]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(new NodeConfigRequest(NodeConfigRequest.defaultTimeout)) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 59203f11f5..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.rest.* -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import java.util -import java.util.function.Supplier -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def nodes = - nodesInCluster.get().asScala.toList - -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 35dfdaaa74..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse](RRTestConfigActionType.name) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 2d936937d3..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index c2da8cabe9..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..2ed1b97248 --- /dev/null +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,33 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse](RRTestSettingsActionType.name) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRTestSettingsActionCannotBeTransported extends Exception + + private [rrtestsettings] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestSettingsActionCannotBeTransported +} + diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..d72dc8acfe --- /dev/null +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es816x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es816x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es816x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es816x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es816x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es816x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..c1c6c8a26b --- /dev/null +++ b/es816x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es816x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 40ae6d97e0..0000000000 --- a/es816x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es816x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es816x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 1f00261ee4..46d402b70a 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -28,7 +28,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,15 +37,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es816x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es816x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es816x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es816x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es816x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es816x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es818x/build.gradle b/es818x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es818x/build.gradle +++ b/es818x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es818x/plugin-metadata/entitlement-policy.yaml b/es818x/plugin-metadata/entitlement-policy.yaml index e48762faa8..ec9ea439be 100644 --- a/es818x/plugin-metadata/entitlement-policy.yaml +++ b/es818x/plugin-metadata/entitlement-policy.yaml @@ -3,6 +3,12 @@ ALL-UNNAMED: - relative_path: ../ relative_to: config mode: read + - path: "/etc/os-release" + mode: "read" + - path: "/usr/lib/os-release" + mode: "read" + - path: "/proc/sys/net/core/somaxconn" + mode: read - manage_threads - inbound_network - outbound_network \ No newline at end of file diff --git a/es818x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es818x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 9a55b9861f..011b4e7216 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es818x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es818x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index b50adedf30..ed16bfcc14 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -47,27 +48,26 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -128,7 +128,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(client), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -162,14 +162,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -180,14 +179,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -205,8 +203,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -229,8 +226,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index 12bfaf5f7b..45c9923dc5 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,7 +23,7 @@ import tech.beshu.ror.accesscontrol.domain.Action.RorAction class RRAdminActionType extends ActionType[RRAdminResponse](RRAdminActionType.name) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 1ecc29436c..7770420170 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -32,10 +32,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 21317a9d83..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.injection.guice.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 942897ce23..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 8e74908a8a..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - public RRConfigsRequest(DiscoveryNode... concreteNodes) { - super(concreteNodes); - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index c0aba7ad0b..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, NodeConfigRequest, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig, Void]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(new NodeConfigRequest(NodeConfigRequest.defaultTimeout)) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 59203f11f5..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.rest.* -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import java.util -import java.util.function.Supplier -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def nodes = - nodesInCluster.get().asScala.toList - -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 35dfdaaa74..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse](RRTestConfigActionType.name) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 2d936937d3..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index c2da8cabe9..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..2ed1b97248 --- /dev/null +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,33 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse](RRTestSettingsActionType.name) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRTestSettingsActionCannotBeTransported extends Exception + + private [rrtestsettings] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestSettingsActionCannotBeTransported +} + diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..d72dc8acfe --- /dev/null +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es818x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es818x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es818x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es818x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es818x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es818x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..c1c6c8a26b --- /dev/null +++ b/es818x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es818x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 40ae6d97e0..0000000000 --- a/es818x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es818x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es818x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 1f00261ee4..46d402b70a 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -28,7 +28,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,15 +37,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es818x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es818x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es818x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es818x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index 6b7344e3ae..ff7bac1b75 100644 --- a/es818x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es818x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configDir(), - modulesPath = environment.modulesDir(), + configDir = File(environment.configDir()), + modulesDir = File(environment.modulesDir()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es81x/build.gradle b/es81x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es81x/build.gradle +++ b/es81x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es81x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es81x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 1916982621..acfef36a08 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es81x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es81x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 6971f11c35..debb9f2bd2 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -37,8 +38,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -49,27 +50,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,14 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -184,14 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -209,8 +207,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -231,8 +228,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 72d125413f..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index d0bd030271..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es81x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es81x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es81x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es81x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es81x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es81x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es81x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es81x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es81x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es81x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es81x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 946cfa4c28..c40f2af9e6 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.SharedGroupFactory import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es81x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es81x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index f9f71f30b1..e78670311e 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,14 +43,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -66,7 +67,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es81x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es81x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es81x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es81x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es82x/build.gradle b/es82x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es82x/build.gradle +++ b/es82x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es82x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es82x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 1916982621..acfef36a08 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es82x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es82x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 6971f11c35..debb9f2bd2 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -37,8 +38,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -49,27 +50,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,14 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -184,14 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -209,8 +207,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -231,8 +228,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 72d125413f..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index d0bd030271..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es82x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es82x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es82x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es82x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es82x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es82x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es82x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es82x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es82x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es82x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es82x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 946cfa4c28..c40f2af9e6 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.SharedGroupFactory import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,14 +37,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es82x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es82x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index f9f71f30b1..e78670311e 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,14 +43,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -66,7 +67,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es82x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es82x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es82x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es82x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es83x/build.gradle b/es83x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es83x/build.gradle +++ b/es83x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es83x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es83x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 1916982621..acfef36a08 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es83x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es83x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 88e01341dc..99e306fd7e 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -37,8 +38,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -49,27 +50,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -98,20 +98,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -134,7 +134,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -166,14 +166,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) networkService: NetworkService, dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -184,14 +183,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -209,8 +207,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -231,8 +228,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index 72d125413f..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import java.util -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index d0bd030271..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{BytesRestResponse, RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new BytesRestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es83x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es83x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es83x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es83x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es83x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es83x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es83x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es83x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es83x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es83x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es83x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 8ccc1290f3..31767410bc 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -26,7 +26,8 @@ import org.elasticsearch.http.{HttpChannel, HttpServerTransport} import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.SharedGroupFactory import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -34,14 +35,15 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + sharedGroupFactory: SharedGroupFactory) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es83x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es83x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index f9f71f30b1..e78670311e 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,14 +43,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -66,7 +67,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es83x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es83x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es83x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es83x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es84x/build.gradle b/es84x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es84x/build.gradle +++ b/es84x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es84x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es84x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 1916982621..acfef36a08 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -110,7 +111,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, client, threadPool, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -224,7 +225,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es84x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es84x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 7a4a37e4e3..ed59594e9e 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -37,8 +38,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -50,27 +51,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -99,20 +99,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s)) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -136,7 +136,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -169,14 +169,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -187,14 +186,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -212,8 +210,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -234,8 +231,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index e545b4bc77..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es84x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es84x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es84x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es84x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es84x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es84x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es84x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es84x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es84x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es84x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es84x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 5631d98c22..b4eb8b51bb 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.tracing.Tracer import org.elasticsearch.transport.netty4.SharedGroupFactory import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es84x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es84x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index f9f71f30b1..e78670311e 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,14 +43,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -66,7 +67,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es84x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es84x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es84x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es84x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es85x/build.gradle b/es85x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es85x/build.gradle +++ b/es85x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es85x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es85x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es85x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es85x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index b177dba85c..6c24256a1c 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -38,8 +39,8 @@ import org.elasticsearch.http.HttpServerTransport import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.script.ScriptService @@ -51,27 +52,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -102,20 +102,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -140,7 +140,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -173,14 +173,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -191,14 +190,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -216,8 +214,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -238,8 +235,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index e545b4bc77..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es85x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es85x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es85x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es85x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es85x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es85x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es85x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es85x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es85x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es85x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es85x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 5631d98c22..b4eb8b51bb 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.tracing.Tracer import org.elasticsearch.transport.netty4.SharedGroupFactory import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es85x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es85x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index f9f71f30b1..e78670311e 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -28,7 +28,9 @@ import org.elasticsearch.common.util.PageCacheRecycler import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -41,14 +43,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode): ChannelHandler = new ClientChannelInitializer { override def initChannel(ch: Channel): Unit = { @@ -66,7 +67,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es85x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es85x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es85x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es85x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es87x/build.gradle b/es87x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es87x/build.gradle +++ b/es87x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es87x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es87x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es87x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es87x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 2234c1e8c5..898a72b80b 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -51,27 +52,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -102,20 +102,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -140,7 +140,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -173,14 +173,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) dispatcher: HttpServerTransport.Dispatcher, clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -191,14 +190,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -216,8 +214,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -238,8 +235,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index e545b4bc77..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es87x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es87x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es87x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es87x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es87x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es87x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es87x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es87x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es87x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es87x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es87x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index d3c8c290b0..0851dcb123 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.tracing.Tracer import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es87x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es87x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index ba64286bb4..8562edfc25 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es87x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es87x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es87x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es87x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es88x/build.gradle b/es88x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es88x/build.gradle +++ b/es88x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es88x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es88x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es88x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es88x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index fc6454f8b7..fb7daa1a3f 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -51,27 +52,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -102,20 +102,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -140,7 +140,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -174,14 +174,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -192,14 +191,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -217,8 +215,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -239,8 +236,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index e545b4bc77..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es88x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es88x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es88x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es88x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es88x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es88x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es88x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es88x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es88x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es88x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es88x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 4a3437a056..473df8e3cb 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.tracing.Tracer import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es88x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es88x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index ba64286bb4..8562edfc25 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.CURRENT, threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es88x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es88x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es88x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es88x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es89x/build.gradle b/es89x/build.gradle index db505e95c2..79196dcd0b 100644 --- a/es89x/build.gradle +++ b/es89x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es89x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es89x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 07d70bbf05..a369e8f646 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -109,7 +110,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, new NodeClientBasedAuditSinkService( client, new XContentJsonParserFactory(xContentRegistry) - )(using environmentConfig.clock) + )(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -223,7 +224,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es89x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es89x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index fc6454f8b7..fb7daa1a3f 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -51,27 +52,26 @@ import org.elasticsearch.watcher.ResourceWatcherService import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.actions.wrappers._upgrade.{RorWrappedUpgradeActionType, TransportRorWrappedUpgradeAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -102,20 +102,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(client: Client, clusterService: ClusterService, @@ -140,7 +140,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(repositoriesServiceSupplier), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -174,14 +174,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -192,14 +191,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -217,8 +215,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -239,8 +236,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index ab68db4ca4..16997c0c09 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -26,7 +26,7 @@ class RRAdminActionType extends ActionType[RRAdminResponse]( ) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 7d647dbbe7..4562b46edc 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -21,33 +21,33 @@ import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.common.xcontent.StatusToXContentObject import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status(): RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 030523983f..7183148074 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -34,10 +34,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 332bb90e1f..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 68e18996bc..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name, RRConfigActionType.reader) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 39e7416033..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - private final NodeConfigRequest nodeConfigRequest; - - @Inject - public RRConfigsRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public RRConfigsRequest(NodeConfigRequest nodeConfigRequest, DiscoveryNode... concreteNodes) { - super(concreteNodes); - this.nodeConfigRequest = nodeConfigRequest; - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } - - @Override - public String toString() { - return "RRConfigsRequest{" + - "concreteNodes=" + Arrays.asList(concreteNodes()) + - '}'; - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 82c715a4b4..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeList(nodes); - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index e545b4bc77..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - request: Writeable.Reader[RRConfigsRequest], - nodeRequest: Writeable.Reader[RRConfigRequest], - nodeExecutor: String, - nodeResponseClass: Class[RRConfig], - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig]( - actionName, - threadPool, - clusterService, - transportService, - actionFilters, - request, - nodeRequest, - nodeExecutor, - nodeResponseClass - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - threadPool, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigsRequest(_), - new RRConfigRequest(_), - ThreadPool.Names.GENERIC, - classOf[RRConfig], - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(request.getNodeConfigRequest) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index a81dc7ccdb..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util -import java.util.function.Supplier - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.core.TimeValue -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.configuration.loader.distributed.{NodeConfigRequest, Timeout} -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - val timeout = getTimeout(request, RestRRConfigAction.defaultTimeout) - val requestConfig = NodeConfigRequest( - timeout = Timeout(timeout.nanos()) - ) - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(requestConfig, nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def getTimeout(request: RestRequest, default: TimeValue) = - request.paramAsTime("timeout", default) - - private def nodes = - nodesInCluster.get().asScala.toList - -} -object RestRRConfigAction { - private val defaultTimeout: TimeValue = toTimeValue(NodeConfigRequest.defaultTimeout) - private def toTimeValue(timeout: Timeout):TimeValue = TimeValue.timeValueNanos(timeout.nanos) -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index b0487bce17..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse]( - RRTestConfigActionType.name, RRTestConfigActionType.exceptionReader -) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index 3426623ea9..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import org.elasticsearch.common.xcontent.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status(): RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 688fd79e44..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.transport.TransportService - -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters) = - this(transportService, actionFilters, ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index 526b1f9237..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.Inject -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..acb9636010 --- /dev/null +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,35 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse]( + RRTestSettingsActionType.name, + RRTestSettingsActionType.exceptionReader +) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRAdminActionCannotBeTransported extends Exception + + def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRAdminActionCannotBeTransported +} \ No newline at end of file diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..e15d4f329a --- /dev/null +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import org.elasticsearch.common.xcontent.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..35e40b8f73 --- /dev/null +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,45 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.transport.TransportService + +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters) = { + this(transportService, actionFilters, ()) + } + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es89x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es89x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es89x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es89x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es89x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es89x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..26296f7491 --- /dev/null +++ b/es89x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.common.inject.Inject +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es89x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 2e75d37d0f..0000000000 --- a/es89x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.common.inject.{Inject, Singleton} -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -@Singleton -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es89x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es89x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 56afacb5bb..ef14b6eb89 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.tracing.Tracer import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es89x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es89x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es89x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es89x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index f01429df0c..406c2de9e7 100644 --- a/es89x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es89x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configFile(), - modulesPath = environment.modulesFile(), + configDir = File(environment.configFile()), + modulesDir = File(environment.configFile()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es90x/build.gradle b/es90x/build.gradle index 6d354330d3..4028b6a7bd 100644 --- a/es90x/build.gradle +++ b/es90x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es90x/plugin-metadata/entitlement-policy.yaml b/es90x/plugin-metadata/entitlement-policy.yaml index e48762faa8..ec9ea439be 100644 --- a/es90x/plugin-metadata/entitlement-policy.yaml +++ b/es90x/plugin-metadata/entitlement-policy.yaml @@ -3,6 +3,12 @@ ALL-UNNAMED: - relative_path: ../ relative_to: config mode: read + - path: "/etc/os-release" + mode: "read" + - path: "/usr/lib/os-release" + mode: "read" + - path: "/proc/sys/net/core/somaxconn" + mode: read - manage_threads - inbound_network - outbound_network \ No newline at end of file diff --git a/es90x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es90x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index c32aac4d9a..2c10ed0012 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -106,7 +107,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def createService(cluster: AuditCluster): IndexBasedAuditSinkService & DataStreamBasedAuditSinkService = { cluster match { case AuditCluster.LocalAuditCluster => - new NodeClientBasedAuditSinkService(client, new XContentJsonParserFactory(xContentRegistry))(using environmentConfig.clock) + new NodeClientBasedAuditSinkService(client, new XContentJsonParserFactory(xContentRegistry))(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -220,7 +221,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es90x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es90x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index cebe4a12fe..638806dbbd 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -24,7 +25,6 @@ import org.elasticsearch.action.{ActionRequest, ActionResponse} import org.elasticsearch.client.internal.node.NodeClient import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.injection.guice.Inject import org.elasticsearch.common.io.stream.NamedWriteableRegistry import org.elasticsearch.common.network.NetworkService import org.elasticsearch.common.settings.* @@ -36,8 +36,9 @@ import org.elasticsearch.http.{HttpPreRequest, HttpServerTransport} import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler +import org.elasticsearch.injection.guice.Inject import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.telemetry.tracing.Tracer @@ -47,26 +48,25 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -97,20 +97,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -127,7 +127,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(client), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -161,14 +161,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -179,14 +178,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -204,8 +202,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler[_ <: ActionRequest, _ <: ActionResponse]]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -227,8 +224,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index 12bfaf5f7b..45c9923dc5 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,7 +23,7 @@ import tech.beshu.ror.accesscontrol.domain.Action.RorAction class RRAdminActionType extends ActionType[RRAdminResponse](RRAdminActionType.name) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 1ecc29436c..7770420170 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -32,10 +32,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 21317a9d83..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.injection.guice.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 942897ce23..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 75d052eb16..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import org.elasticsearch.transport.TransportRequest; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; - -public class RRConfigRequest extends TransportRequest { - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - super(); - this.nodeConfigRequest = nodeConfigRequest; - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigRequestSerializer.serialize(this.nodeConfigRequest)); - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 0db5eda205..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - public RRConfigsRequest(DiscoveryNode... concreteNodes) { - super(concreteNodes); - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index c0aba7ad0b..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, NodeConfigRequest, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig, Void]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(new NodeConfigRequest(NodeConfigRequest.defaultTimeout)) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 59203f11f5..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.rest.* -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import java.util -import java.util.function.Supplier -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def nodes = - nodesInCluster.get().asScala.toList - -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 35dfdaaa74..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse](RRTestConfigActionType.name) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 2d936937d3..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index c2da8cabe9..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..2ed1b97248 --- /dev/null +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,33 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse](RRTestSettingsActionType.name) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRTestSettingsActionCannotBeTransported extends Exception + + private [rrtestsettings] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestSettingsActionCannotBeTransported +} + diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..d72dc8acfe --- /dev/null +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es90x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es90x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es90x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es90x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es90x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es90x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..c1c6c8a26b --- /dev/null +++ b/es90x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es90x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 40ae6d97e0..0000000000 --- a/es90x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es90x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es90x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 1f00261ee4..46d402b70a 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -28,7 +28,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -36,15 +37,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es90x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es90x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es90x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es90x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index 6b7344e3ae..ff7bac1b75 100644 --- a/es90x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es90x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configDir(), - modulesPath = environment.modulesDir(), + configDir = File(environment.configDir()), + modulesDir = File(environment.modulesDir()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es91x/build.gradle b/es91x/build.gradle index 6d354330d3..4028b6a7bd 100644 --- a/es91x/build.gradle +++ b/es91x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es91x/plugin-metadata/entitlement-policy.yaml b/es91x/plugin-metadata/entitlement-policy.yaml index e48762faa8..ec9ea439be 100644 --- a/es91x/plugin-metadata/entitlement-policy.yaml +++ b/es91x/plugin-metadata/entitlement-policy.yaml @@ -3,6 +3,12 @@ ALL-UNNAMED: - relative_path: ../ relative_to: config mode: read + - path: "/etc/os-release" + mode: "read" + - path: "/usr/lib/os-release" + mode: "read" + - path: "/proc/sys/net/core/somaxconn" + mode: read - manage_threads - inbound_network - outbound_network \ No newline at end of file diff --git a/es91x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es91x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 8ecc9c9488..1b4bda121a 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -106,7 +107,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def createService(cluster: AuditCluster): IndexBasedAuditSinkService & DataStreamBasedAuditSinkService = { cluster match { case AuditCluster.LocalAuditCluster => - new NodeClientBasedAuditSinkService(client, new XContentJsonParserFactory(xContentRegistry), threadPool)(using environmentConfig.clock) + new NodeClientBasedAuditSinkService(client, new XContentJsonParserFactory(xContentRegistry), threadPool)(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -220,7 +221,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es91x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es91x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 91537d5537..f20f432a23 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -35,8 +36,8 @@ import org.elasticsearch.http.{HttpPreRequest, HttpServerTransport} import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.telemetry.tracing.Tracer @@ -46,26 +47,25 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,20 +96,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -126,7 +126,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(client), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -160,14 +160,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, tracer: Tracer): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), tracer) } - ) + } .toMap .asJava } @@ -178,14 +177,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -203,8 +201,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -226,8 +223,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index 12bfaf5f7b..45c9923dc5 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,7 +23,7 @@ import tech.beshu.ror.accesscontrol.domain.Action.RorAction class RRAdminActionType extends ActionType[RRAdminResponse](RRAdminActionType.name) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 1ecc29436c..7770420170 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -32,10 +32,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 21317a9d83..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.injection.guice.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 942897ce23..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 426641ac63..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.ActionRequestValidationException; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Objects; - -public class RRConfigRequest extends ActionRequest { - - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - this.nodeConfigRequest = Objects.requireNonNull(nodeConfigRequest); - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); // header - out.writeString(NodeConfigRequestSerializer.serialize(nodeConfigRequest)); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } -} \ No newline at end of file diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 0db5eda205..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - public RRConfigsRequest(DiscoveryNode... concreteNodes) { - super(concreteNodes); - } -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index c0aba7ad0b..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, NodeConfigRequest, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig, Void]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(new NodeConfigRequest(NodeConfigRequest.defaultTimeout)) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 59203f11f5..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.rest.* -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import java.util -import java.util.function.Supplier -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def nodes = - nodesInCluster.get().asScala.toList - -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 35dfdaaa74..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse](RRTestConfigActionType.name) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 2d936937d3..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index c2da8cabe9..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..2ed1b97248 --- /dev/null +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,33 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse](RRTestSettingsActionType.name) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRTestSettingsActionCannotBeTransported extends Exception + + private [rrtestsettings] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestSettingsActionCannotBeTransported +} + diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..d72dc8acfe --- /dev/null +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es91x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es91x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es91x/src/main/scala/tech/beshu/ror/es/handler/AclAwareRequestFilter.scala b/es91x/src/main/scala/tech/beshu/ror/es/handler/AclAwareRequestFilter.scala index 2f6a3aac03..9365230fe3 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/handler/AclAwareRequestFilter.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/handler/AclAwareRequestFilter.scala @@ -75,7 +75,7 @@ import tech.beshu.ror.es.handler.request.context.types.repositories.* import tech.beshu.ror.es.handler.request.context.types.ror.* import tech.beshu.ror.es.handler.request.context.types.snapshots.* import tech.beshu.ror.es.handler.request.context.types.templates.* -import tech.beshu.ror.es.{AtEsLevelUpdateActionResponseListener, HidingInternalErrorDetailsRorActionListener, RorActionListener, RorClusterService, RorRestChannel} +import tech.beshu.ror.es.{HidingInternalErrorDetailsRorActionListener, RorActionListener, RorClusterService, RorRestChannel, AtEsLevelUpdateActionResponseListener} import tech.beshu.ror.implicits.* import tech.beshu.ror.syntax.* diff --git a/es91x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es91x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es91x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es91x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..c1c6c8a26b --- /dev/null +++ b/es91x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es91x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 40ae6d97e0..0000000000 --- a/es91x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es91x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es91x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index ce17639c06..81b7891185 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.telemetry.tracing.Tracer import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - tracer: Tracer, - fipsCompliant: Boolean) + tracer: Tracer) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, tracer, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es91x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es91x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es91x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es91x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index 6b7344e3ae..ff7bac1b75 100644 --- a/es91x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es91x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configDir(), - modulesPath = environment.modulesDir(), + configDir = File(environment.configDir()), + modulesDir = File(environment.modulesDir()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/es92x/build.gradle b/es92x/build.gradle index 6d354330d3..4028b6a7bd 100644 --- a/es92x/build.gradle +++ b/es92x/build.gradle @@ -96,7 +96,7 @@ tasks.register('buildRorPluginZip') { tasks.register('packageRorPlugin', Zip) { dependsOn(cleanOldData, jarHellCheck, toJar, resolvePluginDescriptorTemplate) outputs.upToDateWhen { false } - archivesBaseName = pluginName + archiveBaseName = pluginName into('.') { from configurations.distJars.filter { x -> !x.name.contains('spatial4j') && !x.name.contains('jts') } from 'build/libs/' + pluginFullName + '.jar' diff --git a/es92x/plugin-metadata/entitlement-policy.yaml b/es92x/plugin-metadata/entitlement-policy.yaml index e48762faa8..ec9ea439be 100644 --- a/es92x/plugin-metadata/entitlement-policy.yaml +++ b/es92x/plugin-metadata/entitlement-policy.yaml @@ -3,6 +3,12 @@ ALL-UNNAMED: - relative_path: ../ relative_to: config mode: read + - path: "/etc/os-release" + mode: "read" + - path: "/usr/lib/os-release" + mode: "read" + - path: "/proc/sys/net/core/somaxconn" + mode: read - manage_threads - inbound_network - outbound_network \ No newline at end of file diff --git a/es92x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala b/es92x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala index 8ecc9c9488..1b4bda121a 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/IndexLevelActionFilter.scala @@ -28,6 +28,7 @@ import org.elasticsearch.tasks.Task import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.RemoteClusterService import org.elasticsearch.xcontent.NamedXContentRegistry +import tech.beshu.ror.SystemContext import tech.beshu.ror.accesscontrol.audit.sink.{AuditSinkServiceCreator, DataStreamAndIndexBasedAuditSinkServiceCreator} import tech.beshu.ror.accesscontrol.domain.{Action, AuditCluster} import tech.beshu.ror.accesscontrol.matchers.UniqueIdentifierGenerator @@ -35,12 +36,12 @@ import tech.beshu.ror.boot.* import tech.beshu.ror.boot.ReadonlyRest.StartingFailure import tech.beshu.ror.boot.RorSchedulers.Implicits.mainScheduler import tech.beshu.ror.boot.engines.Engines -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.handler.AclAwareRequestFilter.{EsChain, EsContext} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext.CorrelationIdFrom import tech.beshu.ror.es.handler.response.ForbiddenResponse.createTestSettingsNotConfiguredResponse import tech.beshu.ror.es.handler.{AclAwareRequestFilter, RorNotAvailableRequestHandler} -import tech.beshu.ror.es.services.{EsIndexJsonContentService, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} +import tech.beshu.ror.es.services.{EsIndexDocumentManager, EsServerBasedRorClusterService, NodeClientBasedAuditSinkService, RestClientAuditSinkService} import tech.beshu.ror.es.utils.ThreadContextOps.createThreadContextOps import tech.beshu.ror.es.utils.{EsEnvProvider, ThreadRepo, XContentJsonParserFactory} import tech.beshu.ror.implicits.* @@ -60,20 +61,20 @@ class IndexLevelActionFilter(clusterService: ClusterService, remoteClusterServiceSupplier: Supplier[Option[RemoteClusterService]], repositoriesServiceSupplier: Supplier[Option[RepositoriesService]], esInitListener: EsInitListener, - rorEsConfig: ReadonlyRestEsConfig) - (implicit environmentConfig: EnvironmentConfig) + esConfigBasedRorSettings: EsConfigBasedRorSettings) + (implicit systemContext: SystemContext) extends ActionFilter with Logging { - private implicit val generator: UniqueIdentifierGenerator = environmentConfig.uniqueIdentifierGenerator + private implicit val generator: UniqueIdentifierGenerator = systemContext.uniqueIdentifierGenerator private val rorNotAvailableRequestHandler: RorNotAvailableRequestHandler = - new RorNotAvailableRequestHandler(rorEsConfig.bootConfig) + new RorNotAvailableRequestHandler(esConfigBasedRorSettings.boot) private val esEnv = EsEnvProvider.create(env) private val nodeName = esEnv.esNodeSettings.nodeName private val ror = ReadonlyRest.create( - new EsIndexJsonContentService(client), + new EsIndexDocumentManager(client), auditSinkServiceCreator, esEnv, ) @@ -106,7 +107,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def createService(cluster: AuditCluster): IndexBasedAuditSinkService & DataStreamBasedAuditSinkService = { cluster match { case AuditCluster.LocalAuditCluster => - new NodeClientBasedAuditSinkService(client, new XContentJsonParserFactory(xContentRegistry), threadPool)(using environmentConfig.clock) + new NodeClientBasedAuditSinkService(client, new XContentJsonParserFactory(xContentRegistry), threadPool)(using systemContext.clock) case remote: AuditCluster.RemoteAuditCluster => RestClientAuditSinkService.create(remote) } @@ -220,7 +221,7 @@ class IndexLevelActionFilter(clusterService: ClusterService, private def startRorInstance() = { val startResult = for { _ <- esInitListener.waitUntilReady - result <- ror.start() + result <- ror.start(esConfigBasedRorSettings) } yield result startResult.runAsync { case Right(Right(instance)) => diff --git a/es92x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala b/es92x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala index 5c515491e8..f5425974d8 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/ReadonlyRestPlugin.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es +import cats.implicits.* import monix.execution.Scheduler import monix.execution.schedulers.CanBlock import org.elasticsearch.ElasticsearchException @@ -35,8 +36,8 @@ import org.elasticsearch.http.{HttpPreRequest, HttpServerTransport} import org.elasticsearch.index.IndexModule import org.elasticsearch.index.mapper.IgnoredFieldMapper import org.elasticsearch.indices.breaker.CircuitBreakerService -import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.plugins.* +import org.elasticsearch.plugins.ActionPlugin.ActionHandler import org.elasticsearch.repositories.RepositoriesService import org.elasticsearch.rest.{RestController, RestHandler} import org.elasticsearch.telemetry.TelemetryProvider @@ -46,26 +47,25 @@ import org.elasticsearch.transport.{Transport, TransportInterceptor} import org.elasticsearch.xcontent.NamedXContentRegistry import tech.beshu.ror.boot.{EsInitListener, SecurityProviderConfiguratorForFips} import tech.beshu.ror.buildinfo.LogPluginBuildInfoMessage -import tech.beshu.ror.configuration.{EnvironmentConfig, ReadonlyRestEsConfig} -import tech.beshu.ror.constants +import tech.beshu.ror.settings.es.EsConfigBasedRorSettings import tech.beshu.ror.es.actions.rradmin.rest.RestRRAdminAction import tech.beshu.ror.es.actions.rradmin.{RRAdminActionType, TransportRRAdminAction} import tech.beshu.ror.es.actions.rrauditevent.rest.RestRRAuditEventAction import tech.beshu.ror.es.actions.rrauditevent.{RRAuditEventActionType, TransportRRAuditEventAction} import tech.beshu.ror.es.actions.rrauthmock.rest.RestRRAuthMockAction import tech.beshu.ror.es.actions.rrauthmock.{RRAuthMockActionType, TransportRRAuthMockAction} -import tech.beshu.ror.es.actions.rrconfig.rest.RestRRConfigAction -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, TransportRRConfigAction} import tech.beshu.ror.es.actions.rrmetadata.rest.RestRRUserMetadataAction import tech.beshu.ror.es.actions.rrmetadata.{RRUserMetadataActionType, TransportRRUserMetadataAction} -import tech.beshu.ror.es.actions.rrtestconfig.rest.RestRRTestConfigAction -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, TransportRRTestConfigAction} +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, TransportRRTestSettingsAction} +import tech.beshu.ror.es.actions.rrtestsettings.rest.RestRRTestSettingsAction import tech.beshu.ror.es.actions.wrappers._cat.{RorWrappedCatActionType, TransportRorWrappedCatAction} import tech.beshu.ror.es.dlsfls.RoleIndexSearcherWrapper import tech.beshu.ror.es.ssl.{SSLNetty4HttpServerTransport, SSLNetty4InternodeServerTransport} import tech.beshu.ror.es.utils.{ChannelInterceptingRestHandlerDecorator, EsEnvProvider, EsPatchVerifier, RemoteClusterServiceSupplier} +import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SetOnce +import tech.beshu.ror.{SystemContext, constants} import java.nio.file.Path import java.util @@ -96,20 +96,20 @@ class ReadonlyRestPlugin(s: Settings, p: Path) Netty4Utils.setAvailableProcessors(EsExecutors.NODE_PROCESSORS_SETTING.get(s).roundDown()) } - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default + private implicit val systemContext: SystemContext = SystemContext.default private val environment = new Environment(s, p) private val timeout: FiniteDuration = 10 seconds - private val rorEsConfig = ReadonlyRestEsConfig - .load(EsEnvProvider.create(environment)) - .map(_.fold(e => throw new ElasticsearchException(e.message), identity)) + private val esConfigBasedRorSettings = EsConfigBasedRorSettings + .from(EsEnvProvider.create(environment)) + .map(_.fold(e => throw new ElasticsearchException(e.show), identity)) .runSyncUnsafe(timeout)(Scheduler.global, CanBlock.permit) private val esInitListener = new EsInitListener private val groupFactory = new SetOnce[SharedGroupFactory] private var ilaf: IndexLevelActionFilter = _ - SecurityProviderConfiguratorForFips.configureIfRequired(rorEsConfig.fipsConfig) + esConfigBasedRorSettings.ssl.foreach(SecurityProviderConfiguratorForFips.configureIfRequired) override def createComponents(services: Plugin.PluginServices): util.Collection[_] = { doPrivileged { @@ -126,7 +126,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) new RemoteClusterServiceSupplier(client), () => Some(repositoriesServiceSupplier.get()), esInitListener, - rorEsConfig + esConfigBasedRorSettings ) } List.empty[AnyRef].asJava @@ -160,14 +160,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) perRequestThreadContext: BiConsumer[HttpPreRequest, ThreadContext], clusterSettings: ClusterSettings, telemetryProvider: TelemetryProvider): util.Map[String, Supplier[HttpServerTransport]] = { - rorEsConfig - .sslConfig - .externalSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.externalSsl) + .map { ssl => "ssl_netty4" -> new Supplier[HttpServerTransport] { - override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), telemetryProvider, rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): HttpServerTransport = new SSLNetty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, ssl, clusterSettings, getSharedGroupFactory(settings), telemetryProvider) } - ) + } .toMap .asJava } @@ -178,14 +177,13 @@ class ReadonlyRestPlugin(s: Settings, p: Path) circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService): util.Map[String, Supplier[Transport]] = { - rorEsConfig - .sslConfig - .interNodeSsl - .map(ssl => + esConfigBasedRorSettings + .ssl.flatMap(_.internodeSsl) + .map { ssl => "ror_ssl_internode" -> new Supplier[Transport] { - override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings), rorEsConfig.fipsConfig.isSslFipsCompliant) + override def get(): Transport = new SSLNetty4InternodeServerTransport(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService, ssl, getSharedGroupFactory(settings)) } - ) + } .toMap .asJava } @@ -203,8 +201,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[ActionPlugin.ActionHandler]( new ActionHandler(RRAdminActionType.instance, classOf[TransportRRAdminAction]), new ActionHandler(RRAuthMockActionType.instance, classOf[TransportRRAuthMockAction]), - new ActionHandler(RRTestConfigActionType.instance, classOf[TransportRRTestConfigAction]), - new ActionHandler(RRConfigActionType.instance, classOf[TransportRRConfigAction]), + new ActionHandler(RRTestSettingsActionType.instance, classOf[TransportRRTestSettingsAction]), new ActionHandler(RRUserMetadataActionType.instance, classOf[TransportRRUserMetadataAction]), new ActionHandler(RRAuditEventActionType.instance, classOf[TransportRRAuditEventAction]), // wrappers @@ -226,8 +223,7 @@ class ReadonlyRestPlugin(s: Settings, p: Path) List[RestHandler]( new RestRRAdminAction(), new RestRRAuthMockAction(), - new RestRRTestConfigAction(), - new RestRRConfigAction(nodesInCluster), + new RestRRTestSettingsAction(), new RestRRUserMetadataAction(), new RestRRAuditEventAction() ).asJava diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala index 6babc712b9..3165661004 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionHandler.scala @@ -21,7 +21,7 @@ import monix.execution.Scheduler import org.apache.logging.log4j.scala.Logging import org.elasticsearch.action.ActionListener import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi.ConfigResponse +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse import tech.beshu.ror.boot.RorSchedulers import tech.beshu.ror.implicits.* import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged @@ -42,11 +42,11 @@ class RRAdminActionHandler extends Logging { } } case None => - listener.onFailure(new Exception("Config API is not available")) + listener.onFailure(new Exception("ROR Settings API is not available")) } } - private def handle(result: Either[Throwable, ConfigResponse], + private def handle(result: Either[Throwable, MainSettingsResponse], listener: ActionListener[RRAdminResponse]) (implicit requestId: RequestId): Unit = result match { case Right(response) => @@ -57,5 +57,5 @@ class RRAdminActionHandler extends Logging { } private def getApi = - RorInstanceSupplier.get().map(_.configApi) + RorInstanceSupplier.get().map(_.mainSettingsApi) } diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala index 12bfaf5f7b..45c9923dc5 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminActionType.scala @@ -23,7 +23,7 @@ import tech.beshu.ror.accesscontrol.domain.Action.RorAction class RRAdminActionType extends ActionType[RRAdminResponse](RRAdminActionType.name) object RRAdminActionType { - val name: String = RorAction.RorOldConfigAction.value + val name: String = RorAction.RorRefreshSettingsAction.value val instance = new RRAdminActionType() case object RRAdminActionCannotBeTransported extends Exception def exceptionReader[A]: Writeable.Reader[A] = diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala index ac36f49056..7240997d62 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminRequest.scala @@ -20,15 +20,15 @@ import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException import org.elasticsearch.rest.RestRequest import org.elasticsearch.rest.RestRequest.Method.{GET, POST} import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.ConfigApi +import tech.beshu.ror.api.MainSettingsApi import tech.beshu.ror.constants import tech.beshu.ror.es.actions.RorActionRequest import tech.beshu.ror.utils.ScalaOps.* -class RRAdminRequest(adminApiRequest: ConfigApi.ConfigRequest, +class RRAdminRequest(adminApiRequest: MainSettingsApi.MainSettingsRequest, esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - val getAdminRequest: ConfigApi.ConfigRequest = adminApiRequest + val getAdminRequest: MainSettingsApi.MainSettingsRequest = adminApiRequest lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") override def validate(): ActionRequestValidationException = null @@ -38,19 +38,19 @@ object RRAdminRequest { def createFrom(request: RestRequest): RRAdminRequest = { val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.FORCE_RELOAD_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.ForceReload - case (constants.PROVIDE_FILE_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideFileConfig - case (constants.PROVIDE_INDEX_CONFIG_PATH, GET) => - ConfigApi.ConfigRequest.Type.ProvideIndexConfig - case (constants.UPDATE_INDEX_CONFIG_PATH, POST) => - ConfigApi.ConfigRequest.Type.UpdateIndexConfig + case (constants.FORCE_RELOAD_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.ForceReload + case (constants.PROVIDE_FILE_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideFileSettings + case (constants.PROVIDE_INDEX_SETTINGS_PATH, GET) => + MainSettingsApi.MainSettingsRequest.Type.ProvideIndexSettings + case (constants.UPDATE_INDEX_SETTINGS_PATH, POST) => + MainSettingsApi.MainSettingsRequest.Type.UpdateIndexSettings case (unknownUri, unknownMethod) => throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") } new RRAdminRequest( - new ConfigApi.ConfigRequest(requestType, request.content.utf8ToString), + new MainSettingsApi.MainSettingsRequest(requestType, request.content.utf8ToString), request ) } diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala index 003fddfe63..42ef512263 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/RRAdminResponse.scala @@ -20,34 +20,34 @@ import org.elasticsearch.action.ActionResponse import org.elasticsearch.common.io.stream.StreamOutput import org.elasticsearch.rest.RestStatus import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.ConfigApi -import tech.beshu.ror.api.ConfigApi.ConfigResponse.* -import tech.beshu.ror.api.ConfigApi.* +import tech.beshu.ror.api.MainSettingsApi +import tech.beshu.ror.api.MainSettingsApi.MainSettingsResponse.* +import tech.beshu.ror.api.MainSettingsApi.* import tech.beshu.ror.es.utils.StatusToXContentObject -class RRAdminResponse(response: ConfigApi.ConfigResponse) +class RRAdminResponse(response: MainSettingsApi.MainSettingsResponse) extends ActionResponse with StatusToXContentObject { override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { response match { - case forceReloadConfig: ConfigResponse.ForceReloadConfig => forceReloadConfig match { - case ForceReloadConfig.Success(message) => addResponseJson(builder, response.status, message) - case ForceReloadConfig.Failure(message) => addResponseJson(builder, response.status, message) + case forceReloadSettings: MainSettingsResponse.ForceReloadMainSettings => forceReloadSettings match { + case ForceReloadMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case ForceReloadMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideIndexConfig: ConfigResponse.ProvideIndexConfig => provideIndexConfig match { - case ProvideIndexConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideIndexConfig.ConfigNotFound(message) => addResponseJson(builder, response.status, message) - case ProvideIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideIndexSettings: MainSettingsResponse.ProvideIndexMainSettings => provideIndexSettings match { + case ProvideIndexMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideIndexMainSettings.MainSettingsNotFound(message) => addResponseJson(builder, response.status, message) + case ProvideIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case provideFileConfig: ConfigResponse.ProvideFileConfig => provideFileConfig match { - case ProvideFileConfig.Config(rawConfig) => addResponseJson(builder, response.status, rawConfig) - case ProvideFileConfig.Failure(message) => addResponseJson(builder, response.status, message) + case provideFileSettings: MainSettingsResponse.ProvideFileMainSettings => provideFileSettings match { + case ProvideFileMainSettings.MainSettings(rawSettings) => addResponseJson(builder, response.status, rawSettings) + case ProvideFileMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case updateIndexConfig: ConfigResponse.UpdateIndexConfig => updateIndexConfig match { - case UpdateIndexConfig.Success(message) => addResponseJson(builder, response.status, message) - case UpdateIndexConfig.Failure(message) => addResponseJson(builder, response.status, message) + case updateIndexSettings: MainSettingsResponse.UpdateIndexMainSettings => updateIndexSettings match { + case UpdateIndexMainSettings.Success(message) => addResponseJson(builder, response.status, message) + case UpdateIndexMainSettings.Failure(message) => addResponseJson(builder, response.status, message) } - case failure: ConfigResponse.Failure => failure match { + case failure: MainSettingsResponse.Failure => failure match { case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) } } @@ -58,10 +58,10 @@ class RRAdminResponse(response: ConfigApi.ConfigResponse) override def status: RestStatus = { response match { - case _: ForceReloadConfig => RestStatus.OK - case _: ProvideIndexConfig => RestStatus.OK - case _: ProvideFileConfig => RestStatus.OK - case _: UpdateIndexConfig => RestStatus.OK + case _: ForceReloadMainSettings => RestStatus.OK + case _: ProvideIndexMainSettings => RestStatus.OK + case _: ProvideFileMainSettings => RestStatus.OK + case _: UpdateIndexMainSettings => RestStatus.OK case failure: Failure => failure match { case Failure.BadRequest(_) => RestStatus.BAD_REQUEST } diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala index 1ecc29436c..7770420170 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rradmin/rest/RestRRAdminAction.scala @@ -32,10 +32,10 @@ class RestRRAdminAction extends BaseRestHandler with RestHandler { override def routes(): util.List[Route] = List( - new Route(POST, constants.FORCE_RELOAD_CONFIG_PATH), - new Route(GET, constants.PROVIDE_FILE_CONFIG_PATH), - new Route(GET, constants.PROVIDE_INDEX_CONFIG_PATH), - new Route(POST, constants.UPDATE_INDEX_CONFIG_PATH), + new Route(POST, constants.FORCE_RELOAD_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_FILE_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_INDEX_SETTINGS_PATH), + new Route(POST, constants.UPDATE_INDEX_SETTINGS_PATH), ).asJava override val getName: String = "ror-admin-handler" diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java deleted file mode 100644 index 21317a9d83..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfig.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodeResponse; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.injection.guice.Inject; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfig; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigSerializer; - -import java.io.IOException; - -public class RRConfig extends BaseNodeResponse { - private final NodeConfig nodeConfig; - - @Inject - public RRConfig(StreamInput in) throws IOException { - super(in); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - public RRConfig(DiscoveryNode discoveryNode, NodeConfig nodeConfig) { - super(discoveryNode); - this.nodeConfig = nodeConfig; - } - - public RRConfig(DiscoveryNode discoveryNode, StreamInput in) throws IOException { - super(discoveryNode); - this.nodeConfig = NodeConfigSerializer.parse(in.readString()); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeString(NodeConfigSerializer.serialize(nodeConfig)); - } - - public NodeConfig getNodeConfig() { - return nodeConfig; - } - - @Override - public String toString() { - return "RRConfig{" + - "nodeConfig=" + nodeConfig + ", " + - "discoveryNode=" + getNode() + - '}'; - } -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala deleted file mode 100644 index 942897ce23..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigActionType.scala +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRConfigActionType extends ActionType[RRConfigsResponse](RRConfigActionType.name) - -object RRConfigActionType { - val name: String = RorAction.RorConfigAction.value - val instance = new RRConfigActionType - val reader: Writeable.Reader[RRConfigsResponse] = new RRConfigsResponse(_) -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java deleted file mode 100644 index 426641ac63..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigRequest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.ActionRequest; -import org.elasticsearch.action.ActionRequestValidationException; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Objects; - -public class RRConfigRequest extends ActionRequest { - - private final NodeConfigRequest nodeConfigRequest; - - public RRConfigRequest(NodeConfigRequest nodeConfigRequest) { - this.nodeConfigRequest = Objects.requireNonNull(nodeConfigRequest); - } - - public RRConfigRequest(StreamInput in) throws IOException { - super(in); - this.nodeConfigRequest = NodeConfigRequestSerializer.parse(in.readString()); - } - - public NodeConfigRequest getNodeConfigRequest() { - return nodeConfigRequest; - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); // header - out.writeString(NodeConfigRequestSerializer.serialize(nodeConfigRequest)); - } - - @Override - public ActionRequestValidationException validate() { - return null; - } -} \ No newline at end of file diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java deleted file mode 100644 index 0db5eda205..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.support.nodes.BaseNodesRequest; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; -import tech.beshu.ror.configuration.loader.distributed.NodeConfigRequest; -import tech.beshu.ror.configuration.loader.distributed.internode.NodeConfigRequestSerializer; - -import java.io.IOException; -import java.util.Arrays; - -public class RRConfigsRequest extends BaseNodesRequest { - - public RRConfigsRequest(DiscoveryNode... concreteNodes) { - super(concreteNodes); - } -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java deleted file mode 100644 index 28a9fbeafc..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/RRConfigsResponse.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig; - -import org.elasticsearch.action.FailedNodeException; -import org.elasticsearch.action.support.nodes.BaseNodesResponse; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.common.io.stream.StreamInput; -import org.elasticsearch.common.io.stream.StreamOutput; - -import java.io.IOException; -import java.util.List; - -public class RRConfigsResponse extends BaseNodesResponse { - - protected RRConfigsResponse(StreamInput in) throws IOException { - super(in); - } - - public RRConfigsResponse(ClusterName clusterName, List nodes, List failures) { - super(clusterName, nodes, failures); - } - - @Override - protected List readNodesFrom(StreamInput in) throws IOException { - return in.readCollectionAsList(RRConfig::new); - } - - @Override - protected void writeNodesTo(StreamOutput out, List nodes) throws IOException { - out.writeCollection(nodes); - } -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala deleted file mode 100644 index c0aba7ad0b..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/TransportRRConfigAction.scala +++ /dev/null @@ -1,117 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig - -import cats.implicits.* -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.action.support.ActionFilters -import org.elasticsearch.action.support.nodes.TransportNodesAction -import org.elasticsearch.cluster.node.DiscoveryNode -import org.elasticsearch.cluster.service.ClusterService -import org.elasticsearch.common.io.stream.{StreamInput, Writeable} -import org.elasticsearch.env.Environment -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService -import tech.beshu.ror.configuration.EnvironmentConfig -import tech.beshu.ror.configuration.loader.* -import tech.beshu.ror.configuration.loader.distributed.{NodeConfig, NodeConfigRequest, RawRorConfigLoadingAction, Timeout} -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.services.EsIndexJsonContentService -import tech.beshu.ror.es.utils.EsEnvProvider -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged - -import java.util -import java.util.concurrent.Executor -import scala.annotation.unused -import scala.concurrent.duration.* -import scala.language.postfixOps - -class TransportRRConfigAction(actionName: String, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: IndexJsonContentService, - nodeRequest: Writeable.Reader[RRConfigRequest], - executor: Executor, - @unused constructorDiscriminator: Unit) - extends TransportNodesAction[RRConfigsRequest, RRConfigsResponse, RRConfigRequest, RRConfig, Void]( - actionName, - clusterService, - transportService, - actionFilters, - nodeRequest, - executor - ) { - - import tech.beshu.ror.boot.RorSchedulers.Implicits.rorRestApiScheduler - - private implicit val environmentConfig: EnvironmentConfig = EnvironmentConfig.default - - @Inject - def this(actionName: String, - threadPool: ThreadPool, - clusterService: ClusterService, - transportService: TransportService, - actionFilters: ActionFilters, - env: Environment, - indexContentProvider: EsIndexJsonContentService, - ) = - this( - RRConfigActionType.name, - clusterService, - transportService, - actionFilters, - env, - indexContentProvider, - new RRConfigRequest(_), - threadPool.executor(ThreadPool.Names.GENERIC), - () - ) - - override def newResponse(request: RRConfigsRequest, responses: util.List[RRConfig], failures: util.List[FailedNodeException]): RRConfigsResponse = { - new RRConfigsResponse(clusterService.getClusterName, responses, failures) - } - - override def newNodeResponse(streamInput: StreamInput, discoveryNode: DiscoveryNode): RRConfig = { - new RRConfig(discoveryNode, streamInput) - } - - override def newNodeRequest(request: RRConfigsRequest): RRConfigRequest = - new RRConfigRequest(new NodeConfigRequest(NodeConfigRequest.defaultTimeout)) - - private def loadConfig() = doPrivileged { - RawRorConfigLoadingAction - .loadFromIndex(EsEnvProvider.create(env), indexContentProvider) - .map(_.map(_.map(_.raw))) - } - - override def nodeOperation(request: RRConfigRequest, task: Task): RRConfig = { - val nodeRequest = request.getNodeConfigRequest - val nodeResponse = - loadConfig() - .runSyncUnsafe(toFiniteDuration(nodeRequest.timeout)) - new RRConfig(clusterService.localNode(), NodeConfig(nodeResponse)) - } - - private def toFiniteDuration(timeout: Timeout): FiniteDuration = timeout.nanos nanos - -} - - diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala deleted file mode 100644 index 59203f11f5..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigAction.scala +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.cluster.node.DiscoveryNodes -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.rest.* -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.GET -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.NodeId -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrconfig.{RRConfigActionType, RRConfigsRequest} - -import java.util -import java.util.function.Supplier -import scala.jdk.CollectionConverters.* - -@Inject -class RestRRConfigAction(nodesInCluster: Supplier[DiscoveryNodes]) - extends BaseRestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.MANAGE_ROR_CONFIG_PATH), - ).asJava - - override val getName: String = "ror-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = { - channel => - client.execute( - new RRConfigActionType, - new RRConfigsRequest(nodes.toArray: _*), - new RestRRConfigActionResponseBuilder(NodeId(client.getLocalNodeId), channel) - ) - } - - private def nodes = - nodesInCluster.get().asScala.toList - -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala deleted file mode 100644 index 5f1ba63898..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrconfig/rest/RestRRConfigActionResponseBuilder.scala +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrconfig.rest - -import java.util.concurrent.TimeoutException - -import org.elasticsearch.action.FailedNodeException -import org.elasticsearch.xcontent.XContentBuilder -import org.elasticsearch.rest.action.RestBuilderListener -import org.elasticsearch.rest.{RestChannel, RestResponse, RestStatus} -import org.elasticsearch.transport.ActionNotFoundTransportException -import tech.beshu.ror.configuration.loader.distributed.NodesResponse -import tech.beshu.ror.configuration.loader.distributed.NodesResponse.{NodeError, NodeId, NodeResponse} -import tech.beshu.ror.es.actions.rrconfig.{RRConfig, RRConfigsResponse} - -import scala.jdk.CollectionConverters.* - -final class RestRRConfigActionResponseBuilder(localNode: NodeId, channel: RestChannel) - extends RestBuilderListener[RRConfigsResponse](channel) { - - override def buildResponse(response: RRConfigsResponse, builder: XContentBuilder): RestResponse = { - val nodeResponse = createNodesResponse(response) - new RestResponse(RestStatus.OK, nodeResponse.toJson) - } - - private def createNodesResponse(response: RRConfigsResponse) = - NodesResponse.create( - localNode = localNode, - responses = response.getNodes.asScala.toList.map(createNodeResponse), - failures = response.failures().asScala.toList.map(createNodeError), - ) - - private def createNodeResponse(config: RRConfig) = { - NodeResponse(NodeId(config.getNode.getId), config.getNodeConfig.loadedConfig) - } - - private def createNodeError(failedNodeException: FailedNodeException) = { - NodeError(NodeId(failedNodeException.nodeId()), createCause(failedNodeException)) - } - - private def createCause(failedNodeException: FailedNodeException): NodeError.Cause = { - failedNodeException.getRootCause match { - case _: TimeoutException => - NodeError.Timeout - case _: ActionNotFoundTransportException => - NodeError.RorConfigActionNotFound - case _ => - NodeError.Unknown(failedNodeException.getDetailedMessage) - } - } -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala deleted file mode 100644 index aa8bb31e67..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionHandler.scala +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import cats.implicits.toShow -import monix.execution.Scheduler -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.action.ActionListener -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged -import tech.beshu.ror.utils.RorInstanceSupplier - -class RRTestConfigActionHandler extends Logging { - - private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler - - def handle(request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - getApi match { - case Some(api) => doPrivileged { - implicit val requestId: RequestId = request.requestContextId - api - .call(request.getTestConfigRequest) - .runAsync { result => - handle(result, listener) - } - } - case None => - listener.onFailure(new Exception("TestConfig API is not available")) - } - } - - private def handle(result: Either[Throwable, TestConfigResponse], - listener: ActionListener[RRTestConfigResponse]) - (implicit requestId: RequestId): Unit = result match { - case Right(response) => - listener.onResponse(new RRTestConfigResponse(response)) - case Left(ex) => - logger.error(s"[${requestId.show}] RRTestConfig internal error", ex) - listener.onFailure(new Exception(ex)) - } - - private def getApi = - RorInstanceSupplier.get().map(_.testConfigApi) -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala deleted file mode 100644 index 35dfdaaa74..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigActionType.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionType -import org.elasticsearch.common.io.stream.Writeable -import tech.beshu.ror.accesscontrol.domain.Action.RorAction - -class RRTestConfigActionType extends ActionType[RRTestConfigResponse](RRTestConfigActionType.name) - -object RRTestConfigActionType { - val name: String = RorAction.RorTestConfigAction.value - val instance = new RRTestConfigActionType() - - case object RRTestConfigActionCannotBeTransported extends Exception - - private [rrtestconfig] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestConfigActionCannotBeTransported -} - diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala deleted file mode 100644 index d4574bf654..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigRequest.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} -import org.elasticsearch.rest.RestRequest -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import tech.beshu.ror.accesscontrol.domain.RequestId -import tech.beshu.ror.api.{RorApiRequest, TestConfigApi} -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.RorActionRequest -import tech.beshu.ror.utils.ScalaOps.* - -class RRTestConfigRequest(testConfigApiRequest: TestConfigApi.TestConfigRequest, - esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { - - def getTestConfigRequest: RorApiRequest[TestConfigApi.TestConfigRequest] = - RorApiRequest(testConfigApiRequest, loggerUser) - - lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") - - override def validate(): ActionRequestValidationException = null -} - -object RRTestConfigRequest { - - def createFrom(request: RestRequest): RRTestConfigRequest = { - val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { - case (constants.PROVIDE_TEST_CONFIG_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideTestConfig - case (constants.DELETE_TEST_CONFIG_PATH, DELETE) => - TestConfigApi.TestConfigRequest.Type.InvalidateTestConfig - case (constants.UPDATE_TEST_CONFIG_PATH, POST) => - TestConfigApi.TestConfigRequest.Type.UpdateTestConfig - case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => - TestConfigApi.TestConfigRequest.Type.ProvideLocalUsers - case (unknownUri, unknownMethod) => - throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") - } - new RRTestConfigRequest( - TestConfigApi.TestConfigRequest(requestType, request.content.utf8ToString), - request - ) - } -} - diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala deleted file mode 100644 index d2c63676b7..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/RRTestConfigResponse.scala +++ /dev/null @@ -1,130 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionResponse -import org.elasticsearch.common.io.stream.StreamOutput -import tech.beshu.ror.es.utils.StatusToXContentObject -import org.elasticsearch.rest.RestStatus -import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} -import tech.beshu.ror.api.TestConfigApi -import tech.beshu.ror.api.TestConfigApi.TestConfigResponse.* - -import java.time.ZoneOffset - -class RRTestConfigResponse(response: TestConfigApi.TestConfigResponse) - extends ActionResponse with StatusToXContentObject { - - override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { - response match { - case provideConfigResponse: ProvideTestConfig => provideConfigResponse match { - case res: ProvideTestConfig.CurrentTestSettings => currentConfigJson(builder, res) - case ProvideTestConfig.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case res: ProvideTestConfig.TestSettingsInvalidated => invalidatedConfigJson(builder, res) - } - case updateConfigResponse: UpdateTestConfig => updateConfigResponse match { - case res: UpdateTestConfig.SuccessResponse => updateConfigSuccessResponseJson(builder, res) - case UpdateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case invalidateConfigResponse: InvalidateTestConfig => invalidateConfigResponse match { - case InvalidateTestConfig.SuccessResponse(message) => addResponseJson(builder, response.status, message) - case InvalidateTestConfig.FailedResponse(message) => addResponseJson(builder, response.status, message) - } - case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { - case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) - case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) - case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) - } - case failure: Failure => failure match { - case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) - } - } - builder - } - - override def writeTo(out: StreamOutput): Unit = () - - override def status: RestStatus = response match { - case _: ProvideTestConfig => RestStatus.OK - case _: UpdateTestConfig => RestStatus.OK - case _: InvalidateTestConfig => RestStatus.OK - case _: ProvideLocalUsers => RestStatus.OK - case failure: Failure => failure match { - case Failure.BadRequest(_) => RestStatus.BAD_REQUEST - } - } - - private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { - builder.startObject - builder.field("status", status) - builder.field("message", message) - builder.endObject - } - - private def currentConfigJson(builder: XContentBuilder, response: ProvideTestConfig.CurrentTestSettings): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("ttl", response.ttl.toString()) - builder.field("settings", response.settings.raw) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def invalidatedConfigJson(builder: XContentBuilder, response: ProvideTestConfig.TestSettingsInvalidated): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("settings", response.settings.raw) - builder.field("ttl", response.ttl.toString()) - builder.endObject - } - - private def updateConfigSuccessResponseJson(builder: XContentBuilder, response: UpdateTestConfig.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.field("message", response.message) - builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) - warningsJson(builder, response.warnings) - builder.endObject - } - - private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { - builder.startObject - builder.field("status", response.status) - builder.startArray("users") - response.users.foreach { user => - builder.value(user) - } - builder.endArray() - builder.field("unknown_users", response.unknownUsers) - builder.endObject - } - - private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { - builder.startArray("warnings") - warnings.foreach { warning => - builder.startObject() - builder.field("block_name", warning.blockName) - builder.field("rule_name", warning.ruleName) - builder.field("message", warning.message) - builder.field("hint", warning.hint) - builder.endObject() - } - builder.endArray() - } -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala deleted file mode 100644 index 2d936937d3..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/TransportRRTestConfigAction.scala +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig - -import org.elasticsearch.action.ActionListener -import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.tasks.Task -import org.elasticsearch.threadpool.ThreadPool -import org.elasticsearch.transport.TransportService - -import java.util.concurrent.Executor -import scala.annotation.unused - -class TransportRRTestConfigAction(transportService: TransportService, - actionFilters: ActionFilters, - executor: Executor, - @unused constructorDiscriminator: Unit) - extends HandledTransportAction[RRTestConfigRequest, RRTestConfigResponse]( - RRTestConfigActionType.name, transportService, actionFilters, RRTestConfigActionType.exceptionReader[RRTestConfigRequest], executor - ) { - - @Inject - def this(transportService: TransportService, - actionFilters: ActionFilters, - threadPool: ThreadPool) = - this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) - - private val handler = new RRTestConfigActionHandler() - - override def doExecute(task: Task, request: RRTestConfigRequest, listener: ActionListener[RRTestConfigResponse]): Unit = { - handler.handle(request, listener) - } -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala deleted file mode 100644 index c2da8cabe9..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestconfig/rest/RestRRTestConfigAction.scala +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.actions.rrtestconfig.rest - -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer -import org.elasticsearch.rest.RestHandler.Route -import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} -import org.elasticsearch.rest.* -import tech.beshu.ror.constants -import tech.beshu.ror.es.actions.rrtestconfig.{RRTestConfigActionType, RRTestConfigRequest, RRTestConfigResponse} -import tech.beshu.ror.es.utils.RestToXContentWithStatusListener - -import java.util -import scala.jdk.CollectionConverters.* - -class RestRRTestConfigAction - extends BaseRestHandler with RestHandler { - - override def routes(): util.List[Route] = List( - new Route(GET, constants.PROVIDE_TEST_CONFIG_PATH), - new Route(POST, constants.UPDATE_TEST_CONFIG_PATH), - new Route(DELETE, constants.DELETE_TEST_CONFIG_PATH), - new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) - ).asJava - - override val getName: String = "ror-test-config-handler" - - override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { - private val rorTestConfigRequest = RRTestConfigRequest.createFrom(request) - - override def accept(channel: RestChannel): Unit = { - client.execute(new RRTestConfigActionType, rorTestConfigRequest, new RestToXContentWithStatusListener[RRTestConfigResponse](channel)) - } - } -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala new file mode 100644 index 0000000000..3fe2540c1b --- /dev/null +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionHandler.scala @@ -0,0 +1,61 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import cats.implicits.toShow +import monix.execution.Scheduler +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.action.ActionListener +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.implicits.* +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged +import tech.beshu.ror.utils.RorInstanceSupplier + +class RRTestSettingsActionHandler extends Logging { + + private implicit val rorRestApiScheduler: Scheduler = RorSchedulers.restApiScheduler + + def handle(request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + getApi match { + case Some(api) => doPrivileged { + implicit val requestId: RequestId = request.requestContextId + api + .call(request.getTestSettingsRequest) + .runAsync { result => + handle(result, listener) + } + } + case None => + listener.onFailure(new Exception("ROR Test Settings API is not available")) + } + } + + private def handle(result: Either[Throwable, TestSettingsResponse], + listener: ActionListener[RRTestSettingsResponse]) + (implicit requestId: RequestId): Unit = result match { + case Right(response) => + listener.onResponse(new RRTestSettingsResponse(response)) + case Left(ex) => + logger.error(s"[${requestId.show}] Internal error", ex) + listener.onFailure(new Exception(ex)) + } + + private def getApi = + RorInstanceSupplier.get().map(_.testSettingsApi) +} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala new file mode 100644 index 0000000000..2ed1b97248 --- /dev/null +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsActionType.scala @@ -0,0 +1,33 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionType +import org.elasticsearch.common.io.stream.Writeable +import tech.beshu.ror.accesscontrol.domain.Action.RorAction + +class RRTestSettingsActionType extends ActionType[RRTestSettingsResponse](RRTestSettingsActionType.name) + +object RRTestSettingsActionType { + val name: String = RorAction.RorTestSettingsAction.value + val instance = new RRTestSettingsActionType() + + case object RRTestSettingsActionCannotBeTransported extends Exception + + private [rrtestsettings] def exceptionReader[A]: Writeable.Reader[A] = _ => throw RRTestSettingsActionCannotBeTransported +} + diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala new file mode 100644 index 0000000000..c690c5ecfd --- /dev/null +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsRequest.scala @@ -0,0 +1,60 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.{ActionRequest, ActionRequestValidationException} +import org.elasticsearch.rest.RestRequest +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import tech.beshu.ror.accesscontrol.domain.RequestId +import tech.beshu.ror.api.{RorApiRequest, TestSettingsApi} +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.RorActionRequest +import tech.beshu.ror.utils.ScalaOps.* + +class RRTestSettingsRequest(request: TestSettingsApi.TestSettingsRequest, + esRestRequest: RestRequest) extends ActionRequest with RorActionRequest { + + def getTestSettingsRequest: RorApiRequest[TestSettingsApi.TestSettingsRequest] = + RorApiRequest(request, loggerUser) + + lazy val requestContextId: RequestId = RequestId(s"${esRestRequest.hashCode()}-${this.hashCode()}") + + override def validate(): ActionRequestValidationException = null +} + +object RRTestSettingsRequest { + + def createFrom(request: RestRequest): RRTestSettingsRequest = { + val requestType = (request.uri().addTrailingSlashIfNotPresent(), request.method()) match { + case (constants.PROVIDE_TEST_SETTINGS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideTestSettings + case (constants.DELETE_TEST_SETTINGS_PATH, DELETE) => + TestSettingsApi.TestSettingsRequest.Type.InvalidateTestSettings + case (constants.UPDATE_TEST_SETTINGS_PATH, POST) => + TestSettingsApi.TestSettingsRequest.Type.UpdateTestSettings + case (constants.PROVIDE_LOCAL_USERS_PATH, GET) => + TestSettingsApi.TestSettingsRequest.Type.ProvideLocalUsers + case (unknownUri, unknownMethod) => + throw new IllegalStateException(s"Unknown request: $unknownMethod $unknownUri") + } + new RRTestSettingsRequest( + TestSettingsApi.TestSettingsRequest(requestType, request.content.utf8ToString), + request + ) + } +} + diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala new file mode 100644 index 0000000000..ad72c8e5cf --- /dev/null +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/RRTestSettingsResponse.scala @@ -0,0 +1,130 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionResponse +import org.elasticsearch.common.io.stream.StreamOutput +import tech.beshu.ror.es.utils.StatusToXContentObject +import org.elasticsearch.rest.RestStatus +import org.elasticsearch.xcontent.{ToXContent, XContentBuilder} +import tech.beshu.ror.api.TestSettingsApi +import tech.beshu.ror.api.TestSettingsApi.TestSettingsResponse.* + +import java.time.ZoneOffset + +class RRTestSettingsResponse(response: TestSettingsApi.TestSettingsResponse) + extends ActionResponse with StatusToXContentObject { + + override def toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder = { + response match { + case provideSettingsResponse: ProvideTestSettings => provideSettingsResponse match { + case res: ProvideTestSettings.CurrentTestSettings => currentSettingsJson(builder, res) + case ProvideTestSettings.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case res: ProvideTestSettings.TestSettingsInvalidated => invalidatedSettingsJson(builder, res) + } + case updateSettingsResponse: UpdateTestSettings => updateSettingsResponse match { + case res: UpdateTestSettings.SuccessResponse => updateSettingsSuccessResponseJson(builder, res) + case UpdateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case invalidateSettingsResponse: InvalidateTestSettings => invalidateSettingsResponse match { + case InvalidateTestSettings.SuccessResponse(message) => addResponseJson(builder, response.status, message) + case InvalidateTestSettings.FailedResponse(message) => addResponseJson(builder, response.status, message) + } + case provideUsersResponse: ProvideLocalUsers => provideUsersResponse match { + case res: ProvideLocalUsers.SuccessResponse => provideLocalUsersJson(builder, res) + case ProvideLocalUsers.TestSettingsNotConfigured(message) => addResponseJson(builder, response.status, message) + case ProvideLocalUsers.TestSettingsInvalidated(message) => addResponseJson(builder, response.status, message) + } + case failure: Failure => failure match { + case Failure.BadRequest(message) => addResponseJson(builder, response.status, message) + } + } + builder + } + + override def writeTo(out: StreamOutput): Unit = () + + override def status: RestStatus = response match { + case _: ProvideTestSettings => RestStatus.OK + case _: UpdateTestSettings => RestStatus.OK + case _: InvalidateTestSettings => RestStatus.OK + case _: ProvideLocalUsers => RestStatus.OK + case failure: Failure => failure match { + case Failure.BadRequest(_) => RestStatus.BAD_REQUEST + } + } + + private def addResponseJson(builder: XContentBuilder, status: String, message: String): Unit = { + builder.startObject + builder.field("status", status) + builder.field("message", message) + builder.endObject + } + + private def currentSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.CurrentTestSettings): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("ttl", response.ttl.toString()) + builder.field("settings", response.settings.rawYaml) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def invalidatedSettingsJson(builder: XContentBuilder, response: ProvideTestSettings.TestSettingsInvalidated): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("settings", response.settings.rawYaml) + builder.field("ttl", response.ttl.toString()) + builder.endObject + } + + private def updateSettingsSuccessResponseJson(builder: XContentBuilder, response: UpdateTestSettings.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.field("message", response.message) + builder.field("valid_to", response.validTo.atOffset(ZoneOffset.UTC).toString) + warningsJson(builder, response.warnings) + builder.endObject + } + + private def provideLocalUsersJson(builder: XContentBuilder, response: ProvideLocalUsers.SuccessResponse): Unit = { + builder.startObject + builder.field("status", response.status) + builder.startArray("users") + response.users.foreach { user => + builder.value(user) + } + builder.endArray() + builder.field("unknown_users", response.unknownUsers) + builder.endObject + } + + private def warningsJson(builder: XContentBuilder, warnings: List[Warning]): Unit = { + builder.startArray("warnings") + warnings.foreach { warning => + builder.startObject() + builder.field("block_name", warning.blockName) + builder.field("rule_name", warning.ruleName) + builder.field("message", warning.message) + builder.field("hint", warning.hint) + builder.endObject() + } + builder.endArray() + } +} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala new file mode 100644 index 0000000000..d72dc8acfe --- /dev/null +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/TransportRRTestSettingsAction.scala @@ -0,0 +1,48 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings + +import org.elasticsearch.action.ActionListener +import org.elasticsearch.action.support.{ActionFilters, HandledTransportAction} +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.tasks.Task +import org.elasticsearch.threadpool.ThreadPool +import org.elasticsearch.transport.TransportService + +import java.util.concurrent.Executor +import scala.annotation.unused + +class TransportRRTestSettingsAction(transportService: TransportService, + actionFilters: ActionFilters, + executor: Executor, + @unused constructorDiscriminator: Unit) + extends HandledTransportAction[RRTestSettingsRequest, RRTestSettingsResponse]( + RRTestSettingsActionType.name, transportService, actionFilters, RRTestSettingsActionType.exceptionReader[RRTestSettingsRequest], executor + ) { + + @Inject + def this(transportService: TransportService, + actionFilters: ActionFilters, + threadPool: ThreadPool) = + this(transportService, actionFilters, threadPool.executor(ThreadPool.Names.GENERIC), ()) + + private val handler = new RRTestSettingsActionHandler() + + override def doExecute(task: Task, request: RRTestSettingsRequest, listener: ActionListener[RRTestSettingsResponse]): Unit = { + handler.handle(request, listener) + } +} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala new file mode 100644 index 0000000000..500846af8d --- /dev/null +++ b/es92x/src/main/scala/tech/beshu/ror/es/actions/rrtestsettings/rest/RestRRTestSettingsAction.scala @@ -0,0 +1,50 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.actions.rrtestsettings.rest + +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.rest.BaseRestHandler.RestChannelConsumer +import org.elasticsearch.rest.RestHandler.Route +import org.elasticsearch.rest.RestRequest.Method.{DELETE, GET, POST} +import org.elasticsearch.rest.* +import tech.beshu.ror.constants +import tech.beshu.ror.es.actions.rrtestsettings.{RRTestSettingsActionType, RRTestSettingsRequest, RRTestSettingsResponse} +import tech.beshu.ror.es.utils.RestToXContentWithStatusListener + +import java.util +import scala.jdk.CollectionConverters.* + +class RestRRTestSettingsAction + extends BaseRestHandler with RestHandler { + + override def routes(): util.List[Route] = List( + new Route(GET, constants.PROVIDE_TEST_SETTINGS_PATH), + new Route(POST, constants.UPDATE_TEST_SETTINGS_PATH), + new Route(DELETE, constants.DELETE_TEST_SETTINGS_PATH), + new Route(GET, constants.PROVIDE_LOCAL_USERS_PATH) + ).asJava + + override val getName: String = "ror-test-config-handler" + + override def prepareRequest(request: RestRequest, client: NodeClient): RestChannelConsumer = new RestChannelConsumer { + private val rorTestSettingsRequest = RRTestSettingsRequest.createFrom(request) + + override def accept(channel: RestChannel): Unit = { + client.execute(new RRTestSettingsActionType, rorTestSettingsRequest, new RestToXContentWithStatusListener[RRTestSettingsResponse](channel)) + } + } +} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala b/es92x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala index 494e9150b4..438011eecf 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/dlsfls/RoleIndexSearcherWrapper.scala @@ -54,7 +54,6 @@ object RoleIndexSearcherWrapper extends Logging { } .map(r => (r, r)) case None => - logger.debug(s"FLS: ${constants.FIELDS_TRANSIENT} not found in threadContext") Success((reader, reader)) } } diff --git a/es92x/src/main/scala/tech/beshu/ror/es/handler/AclAwareRequestFilter.scala b/es92x/src/main/scala/tech/beshu/ror/es/handler/AclAwareRequestFilter.scala index 2f6a3aac03..9365230fe3 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/handler/AclAwareRequestFilter.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/handler/AclAwareRequestFilter.scala @@ -75,7 +75,7 @@ import tech.beshu.ror.es.handler.request.context.types.repositories.* import tech.beshu.ror.es.handler.request.context.types.ror.* import tech.beshu.ror.es.handler.request.context.types.snapshots.* import tech.beshu.ror.es.handler.request.context.types.templates.* -import tech.beshu.ror.es.{AtEsLevelUpdateActionResponseListener, HidingInternalErrorDetailsRorActionListener, RorActionListener, RorClusterService, RorRestChannel} +import tech.beshu.ror.es.{HidingInternalErrorDetailsRorActionListener, RorActionListener, RorClusterService, RorRestChannel, AtEsLevelUpdateActionResponseListener} import tech.beshu.ror.implicits.* import tech.beshu.ror.syntax.* diff --git a/es92x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala b/es92x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala index a8727bb24a..9c00906853 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/handler/RorNotAvailableRequestHandler.scala @@ -16,12 +16,12 @@ */ package tech.beshu.ror.es.handler -import tech.beshu.ror.configuration.RorBootConfiguration -import tech.beshu.ror.configuration.RorBootConfiguration.{RorFailedToStartResponse, RorNotStartedResponse} +import tech.beshu.ror.settings.es.RorBootSettings +import tech.beshu.ror.settings.es.RorBootSettings.{RorFailedToStartResponse, RorNotStartedResponse} import tech.beshu.ror.es.handler.AclAwareRequestFilter.EsContext import tech.beshu.ror.es.handler.response.{ForbiddenResponse, ServiceNotAvailableResponse} -final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { +final class RorNotAvailableRequestHandler(settings: RorBootSettings) { def handleRorNotReadyYet(esContext: EsContext): Unit = { val response = prepareNotReadyYetResponse() @@ -34,7 +34,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareNotReadyYetResponse() = { - config.rorNotStartedResponse.httpCode match { + settings.rorNotStartedResponse.httpCode match { case RorNotStartedResponse.HttpCode.`403` => ForbiddenResponse.createRorNotReadyYetResponse() case RorNotStartedResponse.HttpCode.`503` => @@ -43,7 +43,7 @@ final class RorNotAvailableRequestHandler(config: RorBootConfiguration) { } private def prepareFailedToStartResponse() = { - config.rorFailedToStartResponse.httpCode match { + settings.rorFailedToStartResponse.httpCode match { case RorFailedToStartResponse.HttpCode.`403` => ForbiddenResponse.createRorStartingFailureResponse() case RorFailedToStartResponse.HttpCode.`503` => diff --git a/es92x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala b/es92x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala new file mode 100644 index 0000000000..c1c6c8a26b --- /dev/null +++ b/es92x/src/main/scala/tech/beshu/ror/es/services/EsIndexDocumentManager.scala @@ -0,0 +1,118 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.es.services + +import cats.implicits.* +import io.circe.Json +import io.circe.parser.* +import monix.eval.Task +import org.apache.logging.log4j.scala.Logging +import org.elasticsearch.ResourceNotFoundException +import org.elasticsearch.action.support.WriteRequest.RefreshPolicy +import org.elasticsearch.client.internal.node.NodeClient +import org.elasticsearch.index.IndexNotFoundException +import org.elasticsearch.injection.guice.Inject +import org.elasticsearch.xcontent.XContentType +import tech.beshu.ror.accesscontrol.domain.IndexName +import tech.beshu.ror.boot.RorSchedulers +import tech.beshu.ror.es.IndexDocumentManager +import tech.beshu.ror.es.IndexDocumentManager.* +import tech.beshu.ror.implicits.* + +import scala.annotation.unused + +class EsIndexDocumentManager(client: NodeClient, + @unused constructorDiscriminator: Unit) + extends IndexDocumentManager + with Logging { + + @Inject + def this(client: NodeClient) = { + this(client, ()) + } + + override def documentAsJson(index: IndexName.Full, id: String): Task[Either[ReadError, Json]] = { + Task { + client + .get( + client + .prepareGet() + .setIndex(index.name.value) + .setId(id) + .request() + ) + .actionGet() + } + .map { response => + if (response.isExists) { + Option(response.getSourceAsString) match { + case Some(source) => + logger.debug(s"Document [${index.show} ID=$id] _source: $source") + parse(source) match { + case Right(value) => Right(value) + case Left(failure) => throw new IllegalStateException(s"Cannot parse document source to JSON: ${failure.toString}") + } + case None => + logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") + Right(Json.Null) + } + } else { + logger.debug(s"Document [${index.show} ID=$id] not exist") + Left(DocumentNotFound) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case _: IndexNotFoundException => Left(IndexNotFound) + case _: ResourceNotFoundException => Left(DocumentNotFound) + case ex => + logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) + Left(DocumentUnreachable) + } + } + + override def saveDocumentJson(index: IndexName.Full, id: String, document: Json): Task[Either[WriteError, Unit]] = { + Task { + client + .index( + client + .prepareIndex() + .setIndex(index.name.value) + .setId(id) + .setSource(document.noSpaces, XContentType.JSON) + .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) + .request() + ) + .actionGet() + } + .map { response => + response.status().getStatus match { + case status if status / 100 == 2 => + Right(()) + case status => + logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") + Left(CannotWriteToIndex) + } + } + .executeOn(RorSchedulers.blockingScheduler) + .onErrorRecover { + case ex => + logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) + Left(CannotWriteToIndex) + } + } +} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala b/es92x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala deleted file mode 100644 index 40ae6d97e0..0000000000 --- a/es92x/src/main/scala/tech/beshu/ror/es/services/EsIndexJsonContentService.scala +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.es.services - -import cats.implicits.* -import monix.eval.Task -import org.apache.logging.log4j.scala.Logging -import org.elasticsearch.ResourceNotFoundException -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy -import org.elasticsearch.client.internal.node.NodeClient -import org.elasticsearch.injection.guice.Inject -import org.elasticsearch.xcontent.XContentType -import tech.beshu.ror.accesscontrol.domain.IndexName -import tech.beshu.ror.boot.RorSchedulers -import tech.beshu.ror.es.IndexJsonContentService -import tech.beshu.ror.es.IndexJsonContentService.* -import tech.beshu.ror.implicits.* -import tech.beshu.ror.utils.ScalaOps.* - -import scala.annotation.unused -import scala.jdk.CollectionConverters.* - -class EsIndexJsonContentService(client: NodeClient, - @unused constructorDiscriminator: Unit) - extends IndexJsonContentService - with Logging { - - @Inject - def this(client: NodeClient) = { - this(client, ()) - } - - override def sourceOf(index: IndexName.Full, - id: String): Task[Either[ReadError, Map[String, String]]] = { - Task { - client - .get( - client - .prepareGet() - .setIndex(index.name.value) - .setId(id) - .request() - ) - .actionGet() - } - .map { response => - if (response.isExists) { - Option(response.getSourceAsMap) match { - case Some(map) => - val source = map.asScala.toMap.asStringMap - logger.debug(s"Document [${index.show} ID=$id] _source: ${showSource(source)}") - Right(source) - case None => - logger.warn(s"Document [${index.show} ID=$id] _source is not available. Assuming it's empty") - Right(Map.empty[String, String]) - } - } else { - logger.debug(s"Document [${index.show} ID=$id] not exist") - Left(ContentNotFound) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case _: ResourceNotFoundException => Left(ContentNotFound) - case ex => - logger.error(s"Cannot get source of document [${index.show} ID=$id]", ex) - Left(CannotReachContentSource) - } - } - - override def saveContent(index: IndexName.Full, - id: String, - content: Map[String, String]): Task[Either[WriteError, Unit]] = { - Task { - client - .index( - client - .prepareIndex() - .setIndex(index.name.value) - .setId(id) - .setSource(content.asJava, XContentType.JSON) - .setRefreshPolicy(RefreshPolicy.WAIT_UNTIL) - .request() - ) - .actionGet() - } - .map { response => - response.status().getStatus match { - case status if status / 100 == 2 => - Right(()) - case status => - logger.error(s"Cannot write to document [${index.show} ID=$id]. Unexpected response: HTTP $status, response: ${response.toString}") - Left(CannotWriteToIndex) - } - } - .executeOn(RorSchedulers.blockingScheduler) - .onErrorRecover { - case ex => - logger.error(s"Cannot write to document [${index.show} ID=$id]", ex) - Left(CannotWriteToIndex) - } - } - - private def showSource(source: Map[String, String]) = { - ujson.write(source) - } -} diff --git a/es92x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala b/es92x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala index 95fd8dbc81..9180fa06e7 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4HttpServerTransport.scala @@ -27,7 +27,8 @@ import org.elasticsearch.telemetry.TelemetryProvider import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.netty4.{SharedGroupFactory, TLSConfig} import org.elasticsearch.xcontent.NamedXContentRegistry -import tech.beshu.ror.configuration.SslConfiguration.ExternalSslConfiguration +import tech.beshu.ror.settings.es.SslSettings.ExternalSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper class SSLNetty4HttpServerTransport(settings: Settings, @@ -35,15 +36,16 @@ class SSLNetty4HttpServerTransport(settings: Settings, threadPool: ThreadPool, xContentRegistry: NamedXContentRegistry, dispatcher: HttpServerTransport.Dispatcher, - ssl: ExternalSslConfiguration, + ssl: ExternalSslSettings, clusterSettings: ClusterSettings, sharedGroupFactory: SharedGroupFactory, - telemetryProvider: TelemetryProvider, - fipsCompliant: Boolean) + telemetryProvider: TelemetryProvider) extends Netty4HttpServerTransport(settings, networkService, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory, telemetryProvider, TLSConfig.noTLS(), null, null) with Logging { - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, ssl.clientAuthenticationEnabled) + private val serverSslContext = doPrivileged { + SSLCertHelper.prepareServerSSLContext(ssl, ssl.clientAuthenticationEnabled) + } override def configureServerChannelHandler = new SSLHandler(this) diff --git a/es92x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala b/es92x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala index 2fa1d7871c..88b9d3c808 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/ssl/SSLNetty4InternodeServerTransport.scala @@ -29,7 +29,9 @@ import org.elasticsearch.indices.breaker.CircuitBreakerService import org.elasticsearch.threadpool.ThreadPool import org.elasticsearch.transport.ConnectionProfile import org.elasticsearch.transport.netty4.{Netty4Transport, SharedGroupFactory} -import tech.beshu.ror.configuration.SslConfiguration.InternodeSslConfiguration +import tech.beshu.ror.settings.es.RorSslSettings.IsSslFipsCompliant +import tech.beshu.ror.settings.es.SslSettings.InternodeSslSettings +import tech.beshu.ror.utils.AccessControllerHelper.doPrivileged import tech.beshu.ror.utils.SSLCertHelper import tech.beshu.ror.utils.SSLCertHelper.HostAndPort @@ -42,14 +44,13 @@ class SSLNetty4InternodeServerTransport(settings: Settings, circuitBreakerService: CircuitBreakerService, namedWriteableRegistry: NamedWriteableRegistry, networkService: NetworkService, - ssl: InternodeSslConfiguration, - sharedGroupFactory: SharedGroupFactory, - fipsCompliant: Boolean) + ssl: InternodeSslSettings, + sharedGroupFactory: SharedGroupFactory) extends Netty4Transport(settings, TransportVersion.current(), threadPool, networkService, pageCacheRecycler, namedWriteableRegistry, circuitBreakerService, sharedGroupFactory) with Logging { - private val clientSslContext = SSLCertHelper.prepareClientSSLContext(ssl, fipsCompliant, ssl.certificateVerificationEnabled) - private val serverSslContext = SSLCertHelper.prepareServerSSLContext(ssl, fipsCompliant, clientAuthenticationEnabled = false) + private val clientSslContext = doPrivileged { SSLCertHelper.prepareClientSSLContext(ssl) } + private val serverSslContext = doPrivileged { SSLCertHelper.prepareServerSSLContext(ssl, clientAuthenticationEnabled = false) } override def getClientChannelInitializer(node: DiscoveryNode, connectionProfile: ConnectionProfile): ChannelHandler = new ClientChannelInitializer { @@ -68,7 +69,7 @@ class SSLNetty4InternodeServerTransport(settings: Settings, channelHandlerContext = ctx, serverName = Option(node.getAttributes.get("server_name")).map(new SNIHostName(_)), enableHostnameVerification = ssl.hostnameVerificationEnabled, - fipsCompliant = fipsCompliant + fipsCompliant = ssl.fipsMode.isSslFipsCompliant ) ctx.pipeline().replace(this, "internode_ssl_client", new SslHandler(sslEngine)) super.connect(ctx, remoteAddress, localAddress, promise) diff --git a/es92x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala b/es92x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala index 6b7344e3ae..ff7bac1b75 100644 --- a/es92x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala +++ b/es92x/src/main/scala/tech/beshu/ror/es/utils/EsEnvProvider.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.es.utils +import better.files.File import org.elasticsearch.Version import org.elasticsearch.cluster.ClusterName import org.elasticsearch.env.Environment @@ -26,8 +27,8 @@ object EsEnvProvider { def create(environment: Environment): EsEnv = { val settings = environment.settings() EsEnv( - configPath = environment.configDir(), - modulesPath = environment.modulesDir(), + configDir = File(environment.configDir()), + modulesDir = File(environment.modulesDir()), esVersion = EsVersion(major = Version.CURRENT.major, minor = Version.CURRENT.minor, revision = Version.CURRENT.revision), esNodeSettings = EsNodeSettings( nodeName = Node.NODE_NAME_SETTING.get(settings), diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 711ebf5e38..472e5d5117 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip \ No newline at end of file diff --git a/integration-tests/src/test/resources/ror_starting_response_code/malformed_readonlyrest.yml b/integration-tests/src/test/resources/ror_starting_response_code/malformed_readonlyrest.yml new file mode 100644 index 0000000000..ed7ad9a79b --- /dev/null +++ b/integration-tests/src/test/resources/ror_starting_response_code/malformed_readonlyrest.yml @@ -0,0 +1,5 @@ +readonlyrest: + + access_control_rules: + - name: "CONTAINER ADMIN - basic" + auth_key1: "admin:container" \ No newline at end of file diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ActionsSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ActionsSuite.scala index da97abfb79..e69daf7942 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ActionsSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ActionsSuite.scala @@ -31,7 +31,7 @@ class ActionsSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/actions/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/actions/readonlyrest.yml" override val nodeDataInitializer = Some { (esVersion, adminRestClient: RestClient) => { val documentManager = new DocumentManager(adminRestClient, esVersion) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiAuthMockSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiAuthMockSuite.scala index 0018c5a445..f665df4a0d 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiAuthMockSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiAuthMockSuite.scala @@ -24,18 +24,18 @@ import tech.beshu.ror.integration.suites.base.support.BaseManyEsClustersIntegrat import tech.beshu.ror.integration.utils.{ESVersionSupportForAnyWordSpecLike, PluginTestSupport, SingletonLdapContainers} import ujson.Value.Value import tech.beshu.ror.utils.TestUjson.ujson -import tech.beshu.ror.utils.containers.SecurityType.RorWithXpackSecurity import tech.beshu.ror.utils.containers.* import tech.beshu.ror.utils.containers.EsClusterSettings.positiveInt +import tech.beshu.ror.utils.containers.SecurityType.RorWithXpackSecurity import tech.beshu.ror.utils.containers.dependencies.{ldap, wiremock} -import tech.beshu.ror.utils.containers.images.domain.Enabled import tech.beshu.ror.utils.containers.images.ReadonlyRestWithEnabledXpackSecurityPlugin +import tech.beshu.ror.utils.containers.images.domain.Enabled import tech.beshu.ror.utils.elasticsearch.{DocumentManager, IndexManager, RorApiManager, SearchManager} import tech.beshu.ror.utils.misc.CustomScalaTestMatchers import tech.beshu.ror.utils.misc.Resources.getResourceContent -import java.time.{Instant, ZoneOffset} import java.time.temporal.ChronoUnit +import java.time.{Instant, ZoneOffset} import scala.concurrent.duration.* import scala.language.postfixOps @@ -52,9 +52,11 @@ class AdminApiAuthMockSuite override lazy val clusterContainers = NonEmptyList.of(esCluster) override lazy val esTargets = NonEmptyList.fromListUnsafe(esCluster.nodes) - override implicit val rorConfigFileName: String = "/admin_api_mocks/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/admin_api_mocks/readonlyrest.yml" private val readonlyrestIndexName = ".readonlyrest" + private val testSettingsEsDocumentId = "2" + private val settingsReloadInterval = 2 seconds private val esCluster: EsClusterContainer = { def esClusterSettingsCreator(securityType: SecurityType) = @@ -69,9 +71,9 @@ class AdminApiAuthMockSuite createLocalClusterContainer( esClusterSettingsCreator( RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigReloading = Enabled.Yes(2 seconds), + rorSettingsReloading = Enabled.Yes(settingsReloadInterval), rorCustomSettingsIndex = Some(readonlyrestIndexName), - rorConfigFileName = rorConfigFileName + rorSettingsFileName = rorSettingsFileName )) ), ) @@ -84,9 +86,6 @@ class AdminApiAuthMockSuite wiremock(name = "EXT1", portWhenRunningOnWindows = 8081, mappings = "/impersonation/wiremock_service2_ext_user_2.json", "/impersonation/wiremock_group_provider2_gpa_user_2.json"), ) - private val testEngineReloadInterval = 2 seconds - private val testSettingsEsDocumentId = "2" - "An admin Auth Mock REST API" should { "return info that test settings are not configured" when { "get current mocks" in { @@ -192,16 +191,16 @@ class AdminApiAuthMockSuite )) rorClients.foreach { rorApiManager => - val testConfigResponse = rorApiManager.currentRorTestConfig - testConfigResponse.responseJson("status").str should be("TEST_SETTINGS_PRESENT") - testConfigResponse.responseJson("warnings") should be( + val testSettingsResponse = rorApiManager.currentRorTestSettings + testSettingsResponse.responseJson("status").str should be("TEST_SETTINGS_PRESENT") + testSettingsResponse.responseJson("warnings") should be( ujson.read( s""" |[ | { | "block_name": "test2 (1)", | "rule_name": "auth_key_sha1", - | "message": "The rule contains fully hashed username and password. It doesn't support impersonation in this configuration", + | "message": "The rule contains fully hashed username and password. It doesn't support impersonation in this use case.", | "hint": "You can use second version of the rule and use not hashed username. Like that: `auth_key_sha1: USER_NAME:hash(PASSWORD)" | }, | { @@ -650,7 +649,7 @@ class AdminApiAuthMockSuite |""".stripMargin )) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => val response = rorApiManager.currentMockedServices() response should have statusCode 200 @@ -660,7 +659,7 @@ class AdminApiAuthMockSuite } rorClients.foreach { rorApiManager => - val response = rorApiManager.currentRorTestConfig + val response = rorApiManager.currentRorTestSettings response should have statusCode 200 response.responseJson("status").str should be("TEST_SETTINGS_PRESENT") response.responseJson("warnings") should be(ujson.read( @@ -669,7 +668,7 @@ class AdminApiAuthMockSuite | { | "block_name": "test2 (1)", | "rule_name": "auth_key_sha1", - | "message": "The rule contains fully hashed username and password. It doesn't support impersonation in this configuration", + | "message": "The rule contains fully hashed username and password. It doesn't support impersonation in this use case.", | "hint": "You can use second version of the rule and use not hashed username. Like that: `auth_key_sha1: USER_NAME:hash(PASSWORD)" | } |] @@ -677,11 +676,11 @@ class AdminApiAuthMockSuite )) } } - "return info that all mocks are configured when old config version stored in index" in { + "return info that all mocks are configured when old settings version stored in index" in { setupTestSettingsOnAllNodes() invalidateTestSettingsOnAllNodes() - setupTestSettingsInIndex( + setupTestSettingsInIndex(ujson.read( s""" |{ | "ldapMocks": { @@ -762,7 +761,7 @@ class AdminApiAuthMockSuite | } |} |""".stripMargin - ) + )) val payloadServices = ujson.read( s""" @@ -886,7 +885,7 @@ class AdminApiAuthMockSuite |""".stripMargin ) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => val response = rorApiManager.currentMockedServices() response should have statusCode 200 @@ -898,7 +897,7 @@ class AdminApiAuthMockSuite } "provide a method for reload mocked services" which { "is going to reload mocked services" when { - "configuration is correct" when { + "settings are correct" when { "all services are passed" in { setupTestSettingsOnAllNodes() @@ -1234,20 +1233,28 @@ class AdminApiAuthMockSuite } } + override protected def beforeEach(): Unit = { + rorClients.foreach { + _.invalidateImpersonationMocks().force() + } + + removeRorIndexAndAwaitForNotSetTestSettings() + } + private def invalidateTestSettingsOnAllNodes(): Unit = { rorClients.head - .invalidateRorTestConfig() + .invalidateRorTestSettings() .forceOkStatus() - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { - assertTestSettings(_, expectedStatus = "TEST_SETTINGS_INVALIDATED") + assertCurrentTestSettings(_, expectedStatus = "TEST_SETTINGS_INVALIDATED") } } } - private def assertTestSettings(rorApiManager: RorApiManager, expectedStatus: String) = { - val response = rorApiManager.currentRorTestConfig + private def assertCurrentTestSettings(rorApiManager: RorApiManager, expectedStatus: String) = { + val response = rorApiManager.currentRorTestSettings response should have statusCode 200 response.responseJson("status").str should be(expectedStatus) } @@ -1263,38 +1270,29 @@ class AdminApiAuthMockSuite ) } - private def testEngineConfig(): String = esCluster.resolvedRorConfig(getResourceContent(rorConfigFileName)) + private def testSettingsFromFile(): String = esCluster.resolvedRorSettings(getResourceContent(rorSettingsFileName)) override implicit val patienceConfig: PatienceConfig = - PatienceConfig(timeout = testEngineReloadInterval.plus(10 second), interval = 500 millis) - - override protected def beforeEach(): Unit = { - rorClients.foreach { - _ - .invalidateImpersonationMocks() - .force() - } - - removeRorIndexAndAwaitForNotSetTestConfig() - } + PatienceConfig(timeout = settingsReloadInterval.plus(10 second), interval = 500 millis) - private def removeRorIndexAndAwaitForNotSetTestConfig(): Unit = { - // remove index storing test config + private def removeRorIndexAndAwaitForNotSetTestSettings(): Unit = { + // remove index storing test settings new IndexManager(clients.head.basicAuthClient("admin", "container"), esVersionUsed) .removeIndex(readonlyrestIndexName) + .successOrNotFound() - eventually { // await until node invalidate the test config + eventually { // wait until node invalidate the test settings rorClients.foreach { - assertTestSettings(_, expectedStatus = "TEST_SETTINGS_NOT_CONFIGURED") + assertCurrentTestSettings(_, expectedStatus = "TEST_SETTINGS_NOT_CONFIGURED") } } } private def setupTestSettingsInIndex(mocksJson: Value) = { - val testSettings = testEngineConfig() + val testSettings = testSettingsFromFile() val expirationTtl = 30 minutes val expirationTime = Instant.now().plus(expirationTtl.toMillis, ChronoUnit.MILLIS) - val testSettingsJson = ujson.read( + val testSettingsRelatedDocument = ujson.read( s""" |{ | "settings": ${ujson.write(testSettings)}, @@ -1306,8 +1304,9 @@ class AdminApiAuthMockSuite ) val documentType = "settings" val documentManager = new DocumentManager(clients.head.basicAuthClient("admin", "container"), esVersionUsed) - val createDocResponse = documentManager.createDoc(readonlyrestIndexName, documentType, testSettingsEsDocumentId, testSettingsJson) - createDocResponse.isSuccess should be(true) + val createDocResponse = documentManager.createDoc(readonlyrestIndexName, documentType, testSettingsEsDocumentId, testSettingsRelatedDocument) + + createDocResponse should have statusCode 200 } private def assertAuthMocksInIndex(expectedMocks: Value) = { @@ -1316,27 +1315,29 @@ class AdminApiAuthMockSuite indexSearchResponse should have statusCode 200 val indexSearchHits = indexSearchResponse.responseJson("hits")("hits").arr.toList indexSearchHits.size should be >= 1 // at least main document or test document should be present - val testSettingsDocumentHit = indexSearchHits.find { searchResult => - (searchResult("_index").str, searchResult("_id").str) === (readonlyrestIndexName, testSettingsEsDocumentId) - }.value + val testSettingsDocumentHit = indexSearchHits + .find { searchResult => + (searchResult("_index").str, searchResult("_id").str) === (readonlyrestIndexName, testSettingsEsDocumentId) + } + .value - val mocksContent = ujson.read(testSettingsDocumentHit("_source")("auth_services_mocks").str) + val mocksContent = testSettingsDocumentHit("_source")("auth_services_mocks") mocksContent should be(expectedMocks) } private def setupTestSettingsOnAllNodes(): Unit = { rorClients.head - .updateRorTestConfig(testEngineConfig()) + .updateRorTestSettings(testSettingsFromFile()) .forceOkStatus() - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { - assertTestSettings(_, expectedStatus = "TEST_SETTINGS_PRESENT") + assertCurrentTestSettings(_, expectedStatus = "TEST_SETTINGS_PRESENT") } } } - private def rorClients: List[RorApiManager] = { + private lazy val rorClients: List[RorApiManager] = { clients .toList .map(_.adminClient) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiWithDefaultRorIndexSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiWithDefaultRorIndexSuite.scala index c16acb7cfa..66a38f68d3 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiWithDefaultRorIndexSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/AdminApiWithDefaultRorIndexSuite.scala @@ -28,10 +28,10 @@ class AdminApiWithDefaultRorIndexSuite extends BaseAdminApiSuite with PluginTestSupport { - override implicit val rorConfigFileName: String = "/admin_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/admin_api/readonlyrest.yml" override protected val readonlyrestIndexName: String = ".readonlyrest" - override protected lazy val rorWithIndexConfig: EsClusterContainer = { + override protected lazy val rorWithIndexSettings: EsClusterContainer = { def esClusterSettingsCreator(securityType: SecurityType) = EsClusterSettings.create( clusterName = "ROR1", @@ -43,14 +43,14 @@ class AdminApiWithDefaultRorIndexSuite createLocalClusterContainer( esClusterSettingsCreator( RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName, - rorConfigReloading = Enabled.Yes(settingsReloadInterval) + rorSettingsFileName = rorSettingsFileName, + rorSettingsReloading = Enabled.Yes(settingsReloadInterval) )) ) ) } - override protected lazy val rorWithNoIndexConfig: EsClusterContainer = { + override protected lazy val rorWithNoIndexSettings: EsClusterContainer = { def esClusterSettingsCreator(securityType: SecurityType) = EsClusterSettings.create( clusterName = "ROR2", @@ -60,7 +60,7 @@ class AdminApiWithDefaultRorIndexSuite createLocalClusterContainer( esClusterSettingsCreator( RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName + rorSettingsFileName = rorSettingsFileName )) ) ) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CatApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CatApiSuite.scala index f700405df6..04271c39e7 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CatApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CatApiSuite.scala @@ -31,7 +31,7 @@ class CatApiSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/cat_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/cat_api/readonlyrest.yml" private lazy val dev1CatManager = new CatManager(basicAuthClient("dev1", "test"), esVersion = esVersionUsed) private lazy val dev2CatManager = new CatManager(basicAuthClient("dev2", "test"), esVersion = esVersionUsed) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ClosedIndicesSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ClosedIndicesSuite.scala index d8b2b959c2..b28eee05e0 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ClosedIndicesSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ClosedIndicesSuite.scala @@ -31,7 +31,7 @@ class ClosedIndicesSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/closed_indices/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/closed_indices/readonlyrest.yml" override val nodeDataInitializer = Some { (esVersion, adminRestClient: RestClient) => { diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ClusterApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ClusterApiSuite.scala index 30dc5bafd6..ce8ac353db 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ClusterApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ClusterApiSuite.scala @@ -36,7 +36,7 @@ class ClusterApiSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/cluster_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/cluster_api/readonlyrest.yml" override lazy val targetEs = container.nodes.head override lazy val clusterContainer: EsClusterContainer = { @@ -50,7 +50,7 @@ class ClusterApiSuite createLocalClusterContainer( esClusterSettingsCreator( RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName + rorSettingsFileName = rorSettingsFileName )) ) ) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CrossClusterCallsSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CrossClusterCallsSuite.scala index ad33d0220d..d97b5ab44a 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CrossClusterCallsSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CrossClusterCallsSuite.scala @@ -46,7 +46,7 @@ class CrossClusterCallsSuite import tech.beshu.ror.integration.suites.CrossClusterCallsSuite.* - override implicit val rorConfigFileName: String = "/cross_cluster_search/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/cross_cluster_search/readonlyrest.yml" override lazy val targetEs = container.localCluster.nodes.head @@ -54,7 +54,7 @@ class CrossClusterCallsSuite esClusterSettings = EsClusterSettings.create( clusterName = "ROR_L1", securityType = RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName, + rorSettingsFileName = rorSettingsFileName, internodeSsl = Enabled.Yes(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.InternodeSsl.Xpack) )), nodeDataInitializer = localClusterNodeDataInitializer(), @@ -80,7 +80,7 @@ class CrossClusterCallsSuite EsClusterSettings.create( clusterName = name, securityType = RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName, + rorSettingsFileName = rorSettingsFileName, internodeSsl = Enabled.Yes(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.InternodeSsl.Xpack) )), nodeDataInitializer = nodeDataInitializer diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CurrentUserMetadataSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CurrentUserMetadataSuite.scala index ca8617ae80..9c339395ee 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CurrentUserMetadataSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/CurrentUserMetadataSuite.scala @@ -32,7 +32,7 @@ class CurrentUserMetadataSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/current_user_metadata/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/current_user_metadata/readonlyrest.yml" "An ACL" when { "handling current user metadata kibana plugin request (without ROR metadata)" should { diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DataStreamApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DataStreamApiSuite.scala index 3d7819501a..2af81bff2b 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DataStreamApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DataStreamApiSuite.scala @@ -38,7 +38,7 @@ class DataStreamApiSuite with BeforeAndAfterEach with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/data_stream_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/data_stream_api/readonlyrest.yml" private lazy val client = clients.head.adminClient private lazy val user1Client = clients.head.basicAuthClient("user1", "pass") diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DeleteByQuerySuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DeleteByQuerySuite.scala index 17c1ad8b24..87869fb6df 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DeleteByQuerySuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DeleteByQuerySuite.scala @@ -32,7 +32,7 @@ class DeleteByQuerySuite private val matchAllQuery = ujson.read("""{"query" : {"match_all" : {}}}""".stripMargin) - override implicit val rorConfigFileName: String = "/delete_by_query/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/delete_by_query/readonlyrest.yml" override def nodeDataInitializer = Some(ElasticsearchTweetsInitializer) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DocumentApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DocumentApiSuite.scala index 4535c3a26e..3f827071f7 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DocumentApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DocumentApiSuite.scala @@ -34,7 +34,7 @@ class DocumentApiSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/document_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/document_api/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(DocumentApiSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DuplicatedResponseHeadersIssueSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DuplicatedResponseHeadersIssueSuite.scala index 2da23359f4..33253a1fba 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DuplicatedResponseHeadersIssueSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DuplicatedResponseHeadersIssueSuite.scala @@ -34,7 +34,7 @@ class DuplicatedResponseHeadersIssueSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/duplicated_response_headers_issue/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/duplicated_response_headers_issue/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(ElasticsearchTweetsInitializer) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DynamicVariablesSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DynamicVariablesSuite.scala index 1f7dfdfd2f..eb61722673 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DynamicVariablesSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/DynamicVariablesSuite.scala @@ -34,7 +34,7 @@ class DynamicVariablesSuite with SingleClientSupport with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/dynamic_vars/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/dynamic_vars/readonlyrest.yml" override lazy val targetEs = container.nodes.head @@ -54,7 +54,7 @@ class DynamicVariablesSuite createLocalClusterContainer( esClusterSettingsCreator( SecurityType.RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName + rorSettingsFileName = rorSettingsFileName )) ) ) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ExternalAuthenticationSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ExternalAuthenticationSuite.scala index 02f7500182..da76c4be4e 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ExternalAuthenticationSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ExternalAuthenticationSuite.scala @@ -32,7 +32,7 @@ class ExternalAuthenticationSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/external_authentication/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/external_authentication/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(ElasticsearchTweetsInitializer) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FilterRuleSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FilterRuleSuite.scala index edbffeb87d..6b05a11dbc 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FilterRuleSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FilterRuleSuite.scala @@ -34,7 +34,7 @@ class FilterRuleSuite with SingleClientSupport with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/filter_rules/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/filter_rules/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(FilterRuleSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FiltersAndFieldsSecuritySuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FiltersAndFieldsSecuritySuite.scala index 3e4686fcd6..f152227260 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FiltersAndFieldsSecuritySuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FiltersAndFieldsSecuritySuite.scala @@ -33,7 +33,7 @@ class FiltersAndFieldsSecuritySuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/fls_dls/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/fls_dls/readonlyrest.yml" override def nodeDataInitializer = Some(FiltersAndFieldsSecuritySuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FiltersDocLevelSecuritySuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FiltersDocLevelSecuritySuite.scala index 22b12a3a54..1330805d32 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FiltersDocLevelSecuritySuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FiltersDocLevelSecuritySuite.scala @@ -34,7 +34,7 @@ class FiltersDocLevelSecuritySuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/filters/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/filters/readonlyrest.yml" override def nodeDataInitializer = Some(FiltersDocLevelSecuritySuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FipsSslSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FipsSslSuite.scala index 7f0c865e84..2d278ab76e 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FipsSslSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/FipsSslSuite.scala @@ -26,8 +26,8 @@ import tech.beshu.ror.utils.containers.SecurityType.RorSecurity import tech.beshu.ror.utils.containers.images.ReadonlyRestPlugin.Config.{Attributes, InternodeSsl, RestSsl} import tech.beshu.ror.utils.containers.images.domain.{Enabled, SourceFile} import tech.beshu.ror.utils.elasticsearch.CatManager +import tech.beshu.ror.utils.misc.CustomScalaTestMatchers import tech.beshu.ror.utils.misc.OsUtils.ignoreOnWindows -import tech.beshu.ror.utils.misc.{CustomScalaTestMatchers, OsUtils} class FipsSslSuite extends AnyWordSpec @@ -38,7 +38,7 @@ class FipsSslSuite with BeforeAndAfterAll with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/fips_ssl/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/fips_ssl/readonlyrest.yml" override def clusterContainer: EsClusterContainer = generalClusterContainer @@ -49,7 +49,7 @@ class FipsSslSuite clusterName = "fips_cluster", numberOfInstances = positiveInt(2), securityType = RorSecurity(Attributes.default.copy( - rorConfigFileName = rorConfigFileName, + rorSettingsFileName = rorSettingsFileName, restSsl = Enabled.Yes(RestSsl.RorFips(SourceFile.RorFile)), internodeSsl = Enabled.Yes(InternodeSsl.RorFips(SourceFile.RorFile)) )) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/HostsRuleSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/HostsRuleSuite.scala index 03e5e9ec0e..18bca21629 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/HostsRuleSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/HostsRuleSuite.scala @@ -33,7 +33,7 @@ class HostsRuleSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/hosts_rule/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/hosts_rule/readonlyrest.yml" override def nodeDataInitializer = Some(HostsRuleSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ImpersonationSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ImpersonationSuite.scala index 2010c17713..81716f9de4 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ImpersonationSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ImpersonationSuite.scala @@ -38,7 +38,7 @@ class ImpersonationSuite with BeforeAndAfterAll with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/impersonation/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/impersonation/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(ImpersonationSuite.nodeDataInitializer()) @@ -55,7 +55,7 @@ class ImpersonationSuite super.beforeAll() loadTestSettings() rorApiManager - .updateRorInIndexConfig( // In a test, the main engine config should be different from the test config to prevent accidental use of the main engine + .updateRorInIndexSettings( // In a test, the main settings engine should be different from the test settings to prevent accidental use of the main engine s""" |readonlyrest: | access_control_rules: @@ -555,7 +555,7 @@ class ImpersonationSuite } "test engine is not configured" in { impersonatingSearchManagers("admin1", "pass", impersonatedUser = "ldap_user_1").foreach { searchManager => - rorApiManager.invalidateRorTestConfig().forceOkStatus() + rorApiManager.invalidateRorTestSettings().forceOkStatus() loadTestSettings() rorApiManager @@ -593,7 +593,7 @@ class ImpersonationSuite val result1 = searchManager.search("test3_index") result1 should have statusCode 200 - rorApiManager.invalidateRorTestConfig().forceOkStatus() + rorApiManager.invalidateRorTestSettings().forceOkStatus() val result2 = searchManager.search("test3_index") result2 should have statusCode 403 @@ -699,7 +699,7 @@ class ImpersonationSuite private def loadTestSettings(): Unit = { rorApiManager - .updateRorTestConfig(resolvedRorConfigFile.contentAsString) + .updateRorTestSettings(resolvedRorSettingsFile.contentAsString) .forceOkStatus() } diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexAliasesManagementSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexAliasesManagementSuite.scala index 0c02b877af..f8a7723caf 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexAliasesManagementSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexAliasesManagementSuite.scala @@ -33,7 +33,7 @@ class IndexAliasesManagementSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/aliases/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/aliases/readonlyrest.yml" private lazy val adminDocumentManager = new DocumentManager(basicAuthClient("admin", "container"), esVersionUsed) private lazy val adminIndexManager = new IndexManager(basicAuthClient("admin", "container"), esVersionUsed) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexApiWithFreeKibanaSupportSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexApiWithFreeKibanaSupportSuite.scala index f9f09c6043..16193d3119 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexApiWithFreeKibanaSupportSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexApiWithFreeKibanaSupportSuite.scala @@ -24,7 +24,7 @@ class IndexApiWithFreeKibanaSupportSuite extends BaseIndexApiSuite with SingletonPluginTestSupport { - override implicit val rorConfigFileName: String = "/index_api/free_readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/index_api/free_readonlyrest.yml" override val notFoundIndexStatusReturned: Int = 401 override val forbiddenStatusReturned: Int = 401 diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexApiWithNonFreeKibanaSupportSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexApiWithNonFreeKibanaSupportSuite.scala index b5ee37aaa8..4f42189853 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexApiWithNonFreeKibanaSupportSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexApiWithNonFreeKibanaSupportSuite.scala @@ -24,7 +24,7 @@ class IndexApiWithNonFreeKibanaSupportSuite extends BaseIndexApiSuite with SingletonPluginTestSupport { - override implicit val rorConfigFileName: String = "/index_api/nonfree_readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/index_api/nonfree_readonlyrest.yml" override val notFoundIndexStatusReturned: Int = 404 override val forbiddenStatusReturned: Int = 403 diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexLifecycleManagementApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexLifecycleManagementApiSuite.scala index 97e46eb450..36347f40ba 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexLifecycleManagementApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexLifecycleManagementApiSuite.scala @@ -47,7 +47,7 @@ class IndexLifecycleManagementApiSuite with CustomScalaTestMatchers with Eventually { - override implicit val rorConfigFileName: String = "/index_lifecycle_management_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/index_lifecycle_management_api/readonlyrest.yml" override lazy val targetEs = container.nodes.head @@ -62,7 +62,7 @@ class IndexLifecycleManagementApiSuite createLocalClusterContainer( esClusterSettingsCreator( RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName + rorSettingsFileName = rorSettingsFileName )) ) ) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexTemplatesManagementSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexTemplatesManagementSuite.scala index ee14fe34d0..a3b963ec94 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexTemplatesManagementSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndexTemplatesManagementSuite.scala @@ -34,7 +34,7 @@ class IndexTemplatesManagementSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/templates/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/templates/readonlyrest.yml" indexTemplateApiTests("A legacy template API")(new LegacyTemplateManager(_, esVersionUsed)) if (doesSupportIndexTemplates) indexTemplateApiTests("A new template API")(new IndexTemplateManager(_, esVersionUsed)) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndicesReverseWildcardSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndicesReverseWildcardSuite.scala index ed1422b79f..93a775c46c 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndicesReverseWildcardSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/IndicesReverseWildcardSuite.scala @@ -32,7 +32,7 @@ class IndicesReverseWildcardSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/indices_reverse_wildcards/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/indices_reverse_wildcards/readonlyrest.yml" override def nodeDataInitializer = Some(IndicesReverseWildcardSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/JwtAuthSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/JwtAuthSuite.scala index 18e129449c..d11eb2d868 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/JwtAuthSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/JwtAuthSuite.scala @@ -41,7 +41,7 @@ class JwtAuthSuite private val validKeyRole = "1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890" private val wrongKey = "abcdefdsadsadsafdsfsadasdfdsfdfdsfdsfsadsdsaffds" - override implicit val rorConfigFileName: String = "/jwt_auth/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/jwt_auth/readonlyrest.yml" "rejectRequestWithoutAuthorizationHeader" in { val clusterStateManager = new CatManager(noBasicAuthClient, esVersion = esVersionUsed) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LdapIntegrationSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LdapIntegrationSuite.scala index 14a1e969c8..b01a10a0f8 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LdapIntegrationSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LdapIntegrationSuite.scala @@ -33,7 +33,7 @@ class LdapIntegrationSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/ldap_integration/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/ldap_integration/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(LdapIntegrationSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LocalGroupsSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LocalGroupsSuite.scala index 3a94c1e0fc..a90a828d06 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LocalGroupsSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/LocalGroupsSuite.scala @@ -35,7 +35,7 @@ class LocalGroupsSuite with Matchers with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/local_groups/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/local_groups/readonlyrest.yml" "good credentials but with non matching preferred group are sent" in { val clusterManager = new ClusterManager( diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST1Suite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST1Suite.scala index 577f4e50ad..caa991ae04 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST1Suite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST1Suite.scala @@ -50,7 +50,7 @@ class MSearchTEST1Suite val msearchBodyCombo = msearchBodyNotExists ++ msearchBodyQueryWorks ++ msearchBodyEmptyIndex - override implicit val rorConfigFileName: String = "/msearch_test1/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/msearch_test1/readonlyrest.yml" override def nodeDataInitializer = Some(MSearchTEST1Suite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST2Suite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST2Suite.scala index 45eabcecf4..f8c0cad234 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST2Suite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST2Suite.scala @@ -50,7 +50,7 @@ class MSearchTEST2Suite private val msearchBodyCombo = msearchBodyNoMatch ++ msearchBroken ++ msearchBodyEmptyIndex - override implicit val rorConfigFileName: String = "/msearch_test2/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/msearch_test2/readonlyrest.yml" override def nodeDataInitializer = Some(MSearchTEST2Suite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST3Suite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST3Suite.scala index 642148c1f9..be65738a77 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST3Suite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchTEST3Suite.scala @@ -38,7 +38,7 @@ class MSearchTEST3Suite """{"version":true,"size":0,"query":{"match_all":{}}}""" ) - override implicit val rorConfigFileName: String = "/msearch_test3/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/msearch_test3/readonlyrest.yml" override def nodeDataInitializer = Some(MSearchTEST3Suite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchWithFilterSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchWithFilterSuite.scala index a70a64f522..7a2313b60a 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchWithFilterSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MSearchWithFilterSuite.scala @@ -34,7 +34,7 @@ class MSearchWithFilterSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/msearch_with_filter/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/msearch_with_filter/readonlyrest.yml" override def nodeDataInitializer = Some(ElasticsearchTweetsInitializer) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MiscSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MiscSuite.scala index 87585753cd..9dcae5e5a3 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MiscSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/MiscSuite.scala @@ -33,7 +33,7 @@ class MiscSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/misc/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/misc/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(MiscSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ReindexSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ReindexSuite.scala index 048dfac35f..94f384efea 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ReindexSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ReindexSuite.scala @@ -33,7 +33,7 @@ class ReindexSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/reindex/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/reindex/readonlyrest.yml" override def nodeDataInitializer = Some(ReindexSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RemoteReindexSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RemoteReindexSuite.scala index a27d43d113..861c602b4d 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RemoteReindexSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RemoteReindexSuite.scala @@ -41,8 +41,8 @@ class RemoteReindexSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/reindex_multi_containers/readonlyrest_dest_es.yml" - private val sourceEsRorConfigFileName = "/reindex_multi_containers/readonlyrest_source_es.yml" + override implicit val rorSettingsFileName: String = "/reindex_multi_containers/readonlyrest_dest_es.yml" + private val sourceEsRorSettingsFileName = "/reindex_multi_containers/readonlyrest_source_es.yml" private lazy val sourceEsCluster = createLocalClusterContainer( EsClusterSettings.create( @@ -56,7 +56,7 @@ class RemoteReindexSuite }, securityType = SecurityType.RorSecurity( ReadonlyRestPlugin.Config.Attributes.default.copy( - rorConfigFileName = RemoteReindexSuite.this.sourceEsRorConfigFileName, + rorSettingsFileName = RemoteReindexSuite.this.sourceEsRorSettingsFileName, restSsl = Enabled.No )) ) @@ -77,7 +77,7 @@ class RemoteReindexSuite clusterSettingsCreator { SecurityType.RorWithXpackSecurity( ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = RemoteReindexSuite.this.rorConfigFileName, + rorSettingsFileName = RemoteReindexSuite.this.rorSettingsFileName, restSsl = Enabled.No ) ) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ResponseFieldRuleSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ResponseFieldRuleSuite.scala index 84be0d8e60..f148e0a720 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ResponseFieldRuleSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ResponseFieldRuleSuite.scala @@ -32,7 +32,7 @@ class ResponseFieldRuleSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/response_field_rules/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/response_field_rules/readonlyrest.yml" override def nodeDataInitializer = Some(ResponseFieldRuleSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ReverseProxyAuthenticationWithGroupsProviderAuthorizationSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ReverseProxyAuthenticationWithGroupsProviderAuthorizationSuite.scala index 97d2545767..022afae383 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ReverseProxyAuthenticationWithGroupsProviderAuthorizationSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ReverseProxyAuthenticationWithGroupsProviderAuthorizationSuite.scala @@ -32,7 +32,7 @@ class ReverseProxyAuthenticationWithGroupsProviderAuthorizationSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/rev_proxy_groups_provider/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/rev_proxy_groups_provider/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(ElasticsearchTweetsInitializer) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorDisabledSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorDisabledSuite.scala index 8bb59420f7..7898453e71 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorDisabledSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorDisabledSuite.scala @@ -33,7 +33,7 @@ class RorDisabledSuite with SingleClientSupport with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/plugin_disabled/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/plugin_disabled/readonlyrest.yml" override lazy val targetEs = container.nodes.head @@ -41,7 +41,7 @@ class RorDisabledSuite EsClusterSettings.create( clusterName = "ROR1", securityType = RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName, + rorSettingsFileName = rorSettingsFileName, )), ) ) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorKbnAuthSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorKbnAuthSuite.scala index c324fd9d20..124c557caa 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorKbnAuthSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorKbnAuthSuite.scala @@ -37,7 +37,7 @@ class RorKbnAuthSuite private val validKeyRole = "1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890.1234567890" private val wrongKey = "abcdefdsadsadsadsadsadfdsfdsfdsfdsfds" - override implicit val rorConfigFileName: String = "/ror_kbn_auth/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/ror_kbn_auth/readonlyrest.yml" "rejectRequestWithoutAuthorizationHeader" in { val clusterStateManager = new CatManager(noBasicAuthClient, esVersion = esVersionUsed) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorStartingResponseCodeSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorStartingResponseCodeSuite.scala new file mode 100644 index 0000000000..63f7583394 --- /dev/null +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorStartingResponseCodeSuite.scala @@ -0,0 +1,163 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.integration.suites + +import cats.data.NonEmptyList +import monix.eval.Task +import monix.execution.Scheduler +import monix.execution.atomic.AtomicInt +import org.scalatest.wordspec.AnyWordSpec +import tech.beshu.ror.integration.utils.ESVersionSupportForAnyWordSpecLike +import tech.beshu.ror.utils.containers.* +import tech.beshu.ror.utils.containers.ElasticsearchNodeWaitingStrategy.AwaitingReadyStrategy.WaitForEsRestApiResponsive +import tech.beshu.ror.utils.containers.EsContainerCreator.EsNodeSettings +import tech.beshu.ror.utils.containers.images.ReadonlyRestWithEnabledXpackSecurityPlugin +import tech.beshu.ror.utils.elasticsearch.BaseManager.JSON +import tech.beshu.ror.utils.elasticsearch.SearchManager +import tech.beshu.ror.utils.httpclient.RestClient +import tech.beshu.ror.utils.misc.EsModulePatterns + +import scala.concurrent.duration.* +import scala.language.postfixOps +import scala.util.Try + +class RorStartingResponseCodeSuite extends AnyWordSpec with ESVersionSupportForAnyWordSpecLike { + + import RorStartingResponseCodeSuite.* + + implicit val scheduler: Scheduler = Scheduler.computation(10) + + private val validRorSettingsFile = "/ror_starting_response_code/malformed_readonlyrest.yml" + + private val notStartedResponseCodeKey = "readonlyrest.not_started_response_code" + + "ES" when { + "ROR is not started yet" should { + "return not started response with http code 403" when { + "403 configured" in withTestEsContainerManager(Map(notStartedResponseCodeKey -> "403")) { esContainer => + testRorStartup(usingManager = esContainer, expectedResponseCode = 403) + } + "no option configured" in withTestEsContainerManager(Map.empty) { esContainer => + testRorStartup(usingManager = esContainer, expectedResponseCode = 403) + } + } + "return not started response with http code 503" when { + "503 configured" in withTestEsContainerManager(Map(notStartedResponseCodeKey -> "503")) { esContainer => + testRorStartup(usingManager = esContainer, expectedResponseCode = 503) + } + } + } + } + + private def withTestEsContainerManager(additionalEsYamlEntries: Map[String, String]) + (testCode: TestEsContainerManager => Task[Unit]): Unit = { + val esContainer = new TestEsContainerManager( + rorSettingsFile = validRorSettingsFile, + additionalEsYamlEntries = additionalEsYamlEntries + ) + esContainer.start().runSyncUnsafe(5 minutes) + try { + testCode(esContainer) + } finally { + esContainer.stop().runSyncUnsafe() + } + } + + private def testRorStartup(usingManager: TestEsContainerManager, expectedResponseCode: Int): Task[Unit] = { + for { + restClient <- usingManager.createRestClient + searchTestResult <- searchTest(client = restClient) + result <- handleResult(searchTestResult, expectedResponseCode) + } yield result + } + + private def searchTest(client: RestClient): Task[TestResponse] = Task.delay { + val searchManager = new SearchManager(client, esVersionUsed) + val response = searchManager.searchAll("*") + TestResponse(response.responseCode, response.responseJson) + } + + private def handleResult(result: TestResponse, expectedResponseCode: Int): Task[Unit] = { + val isResponseCodeOk = result.responseCode == expectedResponseCode + val isResponseErrorOk = result + .responseJson("error") + .obj("root_cause") + .arr.exists { json => + json("reason").str == "Forbidden by ReadonlyREST" && + json("due_to").str == "READONLYREST_NOT_READY_YET" + } + if (isResponseCodeOk && isResponseErrorOk) { + Task.unit + } else { + Task.raiseError(new IllegalStateException(s"Test failed. Expected success response and ROR failed to start response but was: [$result]")) + } + } +} + +private object RorStartingResponseCodeSuite extends EsModulePatterns { + final case class TestResponse(responseCode: Int, responseJson: JSON) + + private val uniqueClusterId: AtomicInt = AtomicInt(1) + + final class TestEsContainerManager(rorSettingsFile: String, + additionalEsYamlEntries: Map[String, String]) extends EsContainerCreator { + + private val esContainer = createEsContainer + + def start(): Task[Unit] = Task.delay(esContainer.start()) + + def stop(): Task[Unit] = Task.delay(esContainer.stop()) + + def createRestClient: Task[RestClient] = { + Task.tailRecM(()) { _ => + Task.delay(createAdminClient) + } + } + + private def createAdminClient = { + Try(esContainer.adminClient) + .toEither + .left.map(_ => ()) + } + + private def createEsContainer: EsContainer = { + val id = uniqueClusterId.getAndIncrement() + val clusterName = s"startingTest_EsCluster_$id" + val nodeName = s"${clusterName}_1" + create( + nodeSettings = EsNodeSettings( + clusterName = clusterName, + nodeName = nodeName, + securityType = SecurityType.RorWithXpackSecurity( + ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( + rorSettingsFileName = rorSettingsFile, + rorInIndexSettingsLoadingDelay = 5 seconds + ) + ), + containerSpecification = ContainerSpecification.empty.copy( + additionalElasticsearchYamlEntries = additionalEsYamlEntries + ), + esVersion = EsVersion.DeclaredInProject + ), + allNodeNames = NonEmptyList.of(nodeName), + nodeDataInitializer = NoOpElasticsearchNodeDataInitializer, + startedClusterDependencies = StartedClusterDependencies(List.empty), + awaitingReadyStrategy = WaitForEsRestApiResponsive + ) + } + } +} diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorStartingSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorStartingSuite.scala deleted file mode 100644 index 3a9bc161f5..0000000000 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/RorStartingSuite.scala +++ /dev/null @@ -1,199 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.integration.suites - -import cats.data.NonEmptyList -import monix.eval.Task -import monix.execution.Scheduler -import monix.execution.atomic.AtomicInt -import org.scalatest.wordspec.AnyWordSpec -import tech.beshu.ror.integration.utils.ESVersionSupportForAnyWordSpecLike -import tech.beshu.ror.utils.containers.EsContainerCreator.EsNodeSettings -import tech.beshu.ror.utils.containers.* -import tech.beshu.ror.utils.containers.images.ReadonlyRestWithEnabledXpackSecurityPlugin -import tech.beshu.ror.utils.elasticsearch.BaseManager.JSON -import tech.beshu.ror.utils.elasticsearch.SearchManager -import tech.beshu.ror.utils.httpclient.RestClient -import tech.beshu.ror.utils.misc.EsModulePatterns - -import scala.concurrent.duration.* -import scala.language.postfixOps -import scala.util.{Failure, Success, Try} - -class RorStartingSuite extends AnyWordSpec with ESVersionSupportForAnyWordSpecLike { - - import RorStartingSuite.* - - implicit val scheduler: Scheduler = Scheduler.computation(10) - - private val validRorConfigFile = "/basic/readonlyrest.yml" - - private val notStartedResponseCodeKey = "readonlyrest.not_started_response_code" - - "ES" when { - "ROR does not started yet" should { - "return not started response with http code 403" when { - "403 configured" in withTestEsContainerManager(Map(notStartedResponseCodeKey -> "403")) { esContainer => - testRorStartup(usingManager = esContainer, expectedResponseCode = 403) - } - "no option configured" in withTestEsContainerManager(Map.empty) { esContainer => - testRorStartup(usingManager = esContainer, expectedResponseCode = 403) - } - } - "return not started response with http code 503" when { - "503 configured" in withTestEsContainerManager(Map(notStartedResponseCodeKey -> "503")) { esContainer => - testRorStartup(usingManager = esContainer, expectedResponseCode = 503) - } - } - } - } - - private def withTestEsContainerManager(additionalEsYamlEntries: Map[String, String]) - (testCode: TestEsContainerManager => Task[Unit]): Unit = { - val esContainer = new TestEsContainerManager( - rorConfigFile = validRorConfigFile, - additionalEsYamlEntries = additionalEsYamlEntries - ) - try { - Task - .parSequence(List( - testCode(esContainer), - esContainer.start(), - )) - .runSyncUnsafe(5 minutes) - } finally { - esContainer.stop().runSyncUnsafe() - } - } - - private def testRorStartup(usingManager: TestEsContainerManager, expectedResponseCode: Int): Task[Unit] = { - for { - restClient <- usingManager.createRestClient - searchTestResults <- searchTest(client = restClient, searchAttemptsCount = 200) - result <- handleResults(searchTestResults, expectedResponseCode) - } yield result - } - - private def searchTest(client: RestClient, searchAttemptsCount: Int): Task[Seq[TestResponse]] = { - searchAndWait(new SearchManager(client, esVersionUsed), searchAttemptsCount).map(_.distinct) - } - - private def searchAndWait(manager: SearchManager, attemptLeft: Int): Task[List[TestResponse]] = { - if (attemptLeft == 0) - Task.now(List.empty) - else { - for { - currentResponses <- search(manager) - responses <- currentResponses match { - case Right(responses) => for { - _ <- Task.sleep(1 second) - otherResponses <- searchAndWait(manager, attemptLeft - 1) - } yield responses ::: otherResponses - case Left(responses) => - Task.now(responses) - } - } yield responses - } - } - - private def search(manger: SearchManager): Task[Either[List[TestResponse], List[TestResponse]]] = Task.delay { - Try(manger.searchAll("*")) match { - case Success(response) if response.isSuccess => - Left(List(TestResponse(response.responseCode, response.responseJson))) - case Success(response) => - Right(List(TestResponse(response.responseCode, response.responseJson))) - case Failure(_) => - Right(List.empty) - } - } - - private def handleResults(results: Seq[TestResponse], expectedResponseCode: Int): Task[Unit] = { - val hasEsRespondedWithNotStartedResponse = results - .filter { response => - response.responseCode == expectedResponseCode - } - .exists { response => - response - .responseJson("error") - .obj("root_cause") - .arr.exists { json => - json("reason").str == "Forbidden by ReadonlyREST" && - json("due_to").str == "READONLYREST_NOT_READY_YET" - } - } - - val hasEsRespondedWithSuccess = results.exists(_.responseCode == 200) - - if (hasEsRespondedWithNotStartedResponse && hasEsRespondedWithSuccess) { - Task.unit - } else { - Task.raiseError(new IllegalStateException(s"Test failed. Expected success response and ROR failed to start response but was: [$results]")) - } - } -} - -private object RorStartingSuite extends EsModulePatterns { - final case class TestResponse(responseCode: Int, responseJson: JSON) - - private val uniqueClusterId: AtomicInt = AtomicInt(1) - - final class TestEsContainerManager(rorConfigFile: String, - additionalEsYamlEntries: Map[String, String]) extends EsContainerCreator { - - private val esContainer = createEsContainer - - def start(): Task[Unit] = Task.delay(esContainer.start()) - - def stop(): Task[Unit] = Task.delay(esContainer.stop()) - - def createRestClient: Task[RestClient] = { - Task.tailRecM(()) { _ => - Task.delay(createAdminClient) - } - } - - private def createAdminClient = { - Try(esContainer.adminClient) - .toEither - .left.map(_ => ()) - } - - private def createEsContainer: EsContainer = { - val clusterName = s"ROR_${uniqueClusterId.getAndIncrement()}" - val nodeName = s"${clusterName}_1" - create( - nodeSettings = EsNodeSettings( - nodeName = nodeName, - clusterName = clusterName, - securityType = SecurityType.RorWithXpackSecurity( - ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFile, - rorInIndexConfigLoadingDelay = 5 seconds - ) - ), - containerSpecification = ContainerSpecification.empty.copy( - additionalElasticsearchYamlEntries = additionalEsYamlEntries - ), - esVersion = EsVersion.DeclaredInProject - ), - allNodeNames = NonEmptyList.of(nodeName), - nodeDataInitializer = NoOpElasticsearchNodeDataInitializer, - startedClusterDependencies = StartedClusterDependencies(List.empty) - ) - } - } -} diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SearchApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SearchApiSuite.scala index 92dc8589dc..7ff1a96791 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SearchApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SearchApiSuite.scala @@ -39,7 +39,7 @@ class SearchApiSuite with IntegrationPatience with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/search_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/search_api/readonlyrest.yml" override def nodeDataInitializer = Some(SearchApiSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ShrinkIndexApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ShrinkIndexApiSuite.scala index cbfd5dbc70..b68860db0e 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ShrinkIndexApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/ShrinkIndexApiSuite.scala @@ -32,7 +32,7 @@ class ShrinkIndexApiSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/shrink_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/shrink_api/readonlyrest.yml" private lazy val user1IndexManager = new IndexManager(basicAuthClient("dev1", "test"), esVersionUsed) private lazy val user2IndexManager = new IndexManager(basicAuthClient("dev2", "test"), esVersionUsed) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SnapshotAndRestoreApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SnapshotAndRestoreApiSuite.scala index db3731677a..badaaa7187 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SnapshotAndRestoreApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SnapshotAndRestoreApiSuite.scala @@ -40,7 +40,7 @@ class SnapshotAndRestoreApiSuite with CustomScalaTestMatchers with ESVersionSupportForAnyWordSpecLike { - override implicit val rorConfigFileName: String = "/snapshot_and_restore_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/snapshot_and_restore_api/readonlyrest.yml" override def nodeDataInitializer = Some(SnapshotAndRestoreApiSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SplitIndexApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SplitIndexApiSuite.scala index 7cb4f5277a..b4520a2fd5 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SplitIndexApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/SplitIndexApiSuite.scala @@ -32,7 +32,7 @@ class SplitIndexApiSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/split_api/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/split_api/readonlyrest.yml" private lazy val user1IndexManager = new IndexManager(basicAuthClient("dev1", "test"), esVersionUsed) private lazy val user2IndexManager = new IndexManager(basicAuthClient("dev2", "test"), esVersionUsed) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/UriRegexRuleSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/UriRegexRuleSuite.scala index 5c654a6b44..0fe42ecf55 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/UriRegexRuleSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/UriRegexRuleSuite.scala @@ -29,7 +29,7 @@ class UriRegexRuleSuite with ESVersionSupportForAnyWordSpecLike with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/uri_regex_rules/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/uri_regex_rules/readonlyrest.yml" "A uri rule" should { "allow health check" when { diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackApiWithRorWithDisabledXpackSecuritySuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackApiWithRorWithDisabledXpackSecuritySuite.scala index cae7e3ace3..cfffacaf05 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackApiWithRorWithDisabledXpackSecuritySuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackApiWithRorWithDisabledXpackSecuritySuite.scala @@ -25,11 +25,11 @@ import tech.beshu.ror.utils.containers.images.domain.{Enabled, SourceFile} class XpackApiWithRorWithDisabledXpackSecuritySuite extends BaseXpackApiSuite { - override implicit val rorConfigFileName: String = "/xpack_api/readonlyrest_with_ror_ssl.yml" + override implicit val rorSettingsFileName: String = "/xpack_api/readonlyrest_with_ror_ssl.yml" override protected def rorClusterSecurityType: SecurityType = SecurityType.RorSecurity(Attributes.default.copy( - rorConfigFileName = rorConfigFileName, + rorSettingsFileName = rorSettingsFileName, restSsl = Enabled.Yes(RestSsl.Ror(SourceFile.EsFile)), internodeSsl = Enabled.Yes(InternodeSsl.Ror(SourceFile.EsFile)) )) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackApiWithRorWithEnabledXpackSecuritySuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackApiWithRorWithEnabledXpackSecuritySuite.scala index e842d678d7..56ca3dbda9 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackApiWithRorWithEnabledXpackSecuritySuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackApiWithRorWithEnabledXpackSecuritySuite.scala @@ -25,11 +25,11 @@ import tech.beshu.ror.utils.misc.Version class XpackApiWithRorWithEnabledXpackSecuritySuite extends BaseXpackApiSuite { - override implicit val rorConfigFileName: String = "/xpack_api/readonlyrest_without_ror_ssl.yml" + override implicit val rorSettingsFileName: String = "/xpack_api/readonlyrest_without_ror_ssl.yml" override protected def rorClusterSecurityType: SecurityType = SecurityType.RorWithXpackSecurity(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName, + rorSettingsFileName = rorSettingsFileName, restSsl = Enabled.Yes(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.RestSsl.Xpack), internodeSsl = Enabled.Yes(ReadonlyRestWithEnabledXpackSecurityPlugin.Config.InternodeSsl.Xpack) )) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackClusterWithRorNodesAndInternodeSslKeystoreSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackClusterWithRorNodesAndInternodeSslKeystoreSuite.scala index eaf018f394..b0f9430b1f 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackClusterWithRorNodesAndInternodeSslKeystoreSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackClusterWithRorNodesAndInternodeSslKeystoreSuite.scala @@ -23,5 +23,5 @@ class XpackClusterWithRorNodesAndInternodeSslKeystoreSuite extends XpackClusterWithRorNodesAndInternodeSslSuite with PluginTestSupport { - override implicit val rorConfigFileName: String = "/xpack_cluster_with_ror_nodes_and_internode_ssl/readonlyrest_keystore.yml" + override implicit val rorSettingsFileName: String = "/xpack_cluster_with_ror_nodes_and_internode_ssl/readonlyrest_keystore.yml" } diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackClusterWithRorNodesAndInternodeSslPemSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackClusterWithRorNodesAndInternodeSslPemSuite.scala index 82c6d58f5f..fa2bb23ea0 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackClusterWithRorNodesAndInternodeSslPemSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/XpackClusterWithRorNodesAndInternodeSslPemSuite.scala @@ -23,5 +23,5 @@ class XpackClusterWithRorNodesAndInternodeSslPemSuite extends XpackClusterWithRorNodesAndInternodeSslSuite with PluginTestSupport { - override implicit val rorConfigFileName: String = "/xpack_cluster_with_ror_nodes_and_internode_ssl/readonlyrest_pem.yml" + override implicit val rorSettingsFileName: String = "/xpack_cluster_with_ror_nodes_and_internode_ssl/readonlyrest_pem.yml" } diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/DisabledAuditingToolsSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/DisabledAuditingToolsSuite.scala index ad31e913de..8b28a627b5 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/DisabledAuditingToolsSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/DisabledAuditingToolsSuite.scala @@ -31,7 +31,7 @@ class DisabledAuditingToolsSuite with BeforeAndAfterEach with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/ror_audit/disabled_auditing_tools/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/ror_audit/disabled_auditing_tools/readonlyrest.yml" override val nodeDataInitializer = Some(ElasticsearchTweetsInitializer) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/LocalClusterAuditingToolsSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/LocalClusterAuditingToolsSuite.scala index 2a89be4397..3e93efd8b7 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/LocalClusterAuditingToolsSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/LocalClusterAuditingToolsSuite.scala @@ -36,7 +36,7 @@ class LocalClusterAuditingToolsSuite private val isDataStreamSupported = Version.greaterOrEqualThan(esVersionUsed, 7, 9, 0) - override implicit val rorConfigFileName: String = { + override implicit val rorSettingsFileName: String = { if (isDataStreamSupported) { "/ror_audit/enabled_auditing_tools/readonlyrest.yml" } else { @@ -48,12 +48,12 @@ class LocalClusterAuditingToolsSuite override lazy val destNodesClientProviders: NonEmptyList[ClientProvider] = NonEmptyList.of(this) - override def baseRorConfig: String = resolvedRorConfigFile.contentAsString + override def baseRorSettingsYaml: String = resolvedRorSettingsFile.contentAsString override protected def baseAuditDataStreamName: Option[String] = Option.when(Version.greaterOrEqualThan(esVersionUsed, 7, 9, 0))("audit_data_stream") - // Adding the ES cluster fields is disabled in the /enabled_auditing_tools/readonlyrest.yml config file (`DefaultAuditLogSerializerV1` is used) + // Adding the ES cluster fields is disabled in the /enabled_auditing_tools/readonlyrest.yml settings file (`DefaultAuditLogSerializerV1` is used) override def assertForEveryAuditEntry(entry: JSON): Unit = { entry.obj.get("es_node_name") shouldBe None entry.obj.get("es_cluster_name") shouldBe None @@ -61,13 +61,13 @@ class LocalClusterAuditingToolsSuite "ES" should { "submit audit entries" when { - "first request uses V1 serializer, then ROR config is reloaded and second request uses V2 serializer" in { + "first request uses V1 serializer, then ROR settings is reloaded and second request uses V2 serializer" in { val indexManager = new IndexManager(basicAuthClient("username", "dev"), esVersionUsed) - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") performAndAssertExampleSearchRequest(indexManager) - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV2") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV2") performAndAssertExampleSearchRequest(indexManager) forEachAuditManager { adminAuditManager => @@ -75,7 +75,7 @@ class LocalClusterAuditingToolsSuite val auditEntries = adminAuditManager.getEntries.force().jsons // On Linux we could assert number of entries equal to 2. - // On Windows reloading config sometimes takes a little longer, + // On Windows reloading settings sometimes takes a little longer, // and there are 3 or more messages (from before reload, so not important) auditEntries.size should be >= 2 auditEntries.exists(entry => @@ -98,12 +98,12 @@ class LocalClusterAuditingToolsSuite ) shouldBe true } } - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") } "using ReportingAllEventsAuditLogSerializer" in { val indexManager = new IndexManager(basicAuthClient("username", "dev"), esVersionUsed) - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.FullAuditLogSerializer") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.FullAuditLogSerializer") performAndAssertExampleSearchRequest(indexManager) forEachAuditManager { adminAuditManager => @@ -126,14 +126,14 @@ class LocalClusterAuditingToolsSuite auditEntries.exists(entry => entry("path").str == "/audit_index/_search/") shouldBe true } } - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") // This test uses serializer, that reports all events. We need to wait a moment, to ensure that there will be no more events using that serializer Thread.sleep(3000) } "using ReportingAllEventsWithQueryAuditLogSerializer" in { val indexManager = new IndexManager(basicAuthClient("username", "dev"), esVersionUsed) - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.FullAuditLogWithQuerySerializer") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.FullAuditLogWithQuerySerializer") performAndAssertExampleSearchRequest(indexManager) forEachAuditManager { adminAuditManager => @@ -155,15 +155,15 @@ class LocalClusterAuditingToolsSuite auditEntries.exists(entry => entry("path").str == "/audit_index/_search/") shouldBe true } } - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") // This test uses serializer, that reports all events. We need to wait a moment, to ensure that there will be no more events using that serializer Thread.sleep(3000) } "using ConfigurableQueryAuditLogSerializer" in { val indexManager = new IndexManager(basicAuthClient("username", "dev"), esVersionUsed) - // Change config to use configurable serializer and perform the request - updateRorConfig( + // Change setting to use configurable serializer and perform the request + updateRorSettings( originalString = """type: "static"""", newString = """type: "configurable"""", ) @@ -186,7 +186,7 @@ class LocalClusterAuditingToolsSuite } // Disable audit for Rule 1, clean managers, perform second request - updateRorConfig( + updateRorSettings( "enabled: true ## twitter audit toggle", "enabled: false ## twitter audit toggle", ) @@ -200,19 +200,19 @@ class LocalClusterAuditingToolsSuite auditEntries.size shouldBe 0 } - // Restore the default config - updateRorConfig( + // Restore the default settings + updateRorSettings( "enabled: false ## twitter audit toggle", "enabled: true ## twitter audit toggle", ) - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") } "using ECS serializer" in { val indexManager = new IndexManager(basicAuthClient("username", "dev"), esVersionUsed) // We need to create a new index with a different name for this test, because the ECS schema // is not compatible with the Json object created by other serializers in previous tests. val ecsAuditIndexName = "ecs_audit_index" - updateRorConfig( + updateRorSettings( replacements = Map( """type: "static"""" -> """type: "ecs"""", "audit_index" -> ecsAuditIndexName, @@ -259,7 +259,7 @@ class LocalClusterAuditingToolsSuite entry("labels")("ror_detailed_reason").str == "{ name: 'Rule 1', policy: ALLOW, rules: [auth_key, methods, indices]" } shouldBe true } - updateRorConfigToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") + updateRorSettingsToUseSerializer("tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1") } } } @@ -269,20 +269,21 @@ class LocalClusterAuditingToolsSuite response should have statusCode 200 } - private def updateRorConfigToUseSerializer(serializer: String) = updateRorConfig( - originalString = """class_name: "tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1"""", - newString = s"""class_name: "$serializer"""" - ) + private def updateRorSettingsToUseSerializer(serializer: String): Unit = + updateRorSettings( + originalString = """class_name: "tech.beshu.ror.audit.instances.DefaultAuditLogSerializerV1"""", + newString = s"""class_name: "$serializer"""" + ) - private def updateRorConfig(originalString: String, newString: String): Unit = - updateRorConfig(Map(originalString -> newString)) + private def updateRorSettings(originalString: String, newString: String): Unit = + updateRorSettings(Map(originalString -> newString)) - private def updateRorConfig(replacements: Map[String, String]): Unit = { - val initialConfig = getResourceContent(rorConfigFileName) - val modifiedConfig = replacements.foldLeft(initialConfig) { case (soFar, (originalString, newString)) => + private def updateRorSettings(replacements: Map[String, String]): Unit = { + val initialSettings = getResourceContent(rorSettingsFileName) + val modifiedSettings = replacements.foldLeft(initialSettings) { case (soFar, (originalString, newString)) => soFar.replace(originalString, newString) } - rorApiManager.updateRorInIndexConfig(modifiedConfig).forceOKStatusOrConfigAlreadyLoaded() - rorApiManager.reloadRorConfig().force() + rorApiManager.updateRorInIndexSettings(modifiedSettings).forceOKStatusOrSettingsAlreadyLoaded() + rorApiManager.reloadRorSettings().force() } } diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/QueryAuditLogSerializerSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/QueryAuditLogSerializerSuite.scala index 14716ca28b..dcd3a102f8 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/QueryAuditLogSerializerSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/QueryAuditLogSerializerSuite.scala @@ -35,7 +35,7 @@ class QueryAuditLogSerializerSuite with BeforeAndAfterEach with CustomScalaTestMatchers { - override implicit val rorConfigFileName: String = "/ror_audit/query_audit_log_serializer/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/ror_audit/query_audit_log_serializer/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(ElasticsearchTweetsInitializer) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/RemoteClusterAuditingToolsSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/RemoteClusterAuditingToolsSuite.scala index 155a553e04..783b61de00 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/RemoteClusterAuditingToolsSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/audit/RemoteClusterAuditingToolsSuite.scala @@ -39,7 +39,7 @@ class RemoteClusterAuditingToolsSuite private val isDataStreamSupported = Version.greaterOrEqualThan(esVersionUsed, 7, 9, 0) - override implicit val rorConfigFileName: String = { + override implicit val rorSettingsFileName: String = { if (isDataStreamSupported) { "/ror_audit/cluster_auditing_tools/readonlyrest.yml" } else { @@ -89,11 +89,11 @@ class RemoteClusterAuditingToolsSuite override lazy val destNodesClientProviders: NonEmptyList[ClientProvider] = NonEmptyList.fromListUnsafe(auditEsContainers) - override protected def baseRorConfig: String = resolvedRorConfigFile.contentAsString + override protected def baseRorSettingsYaml: String = resolvedRorSettingsFile.contentAsString override protected def baseAuditDataStreamName: Option[String] = Option.when(isDataStreamSupported)("audit_data_stream") - // Adding the ES cluster fields is enabled in the /cluster_auditing_tools/readonlyrest.yml config file (`DefaultAuditLogSerializerV2` is used) + // Adding the ES cluster fields is enabled in the /cluster_auditing_tools/readonlyrest.yml settings file (`DefaultAuditLogSerializerV2` is used) override def assertForEveryAuditEntry(entry: JSON): Unit = { entry("es_node_name").str shouldBe "ROR_SINGLE_1" entry("es_cluster_name").str shouldBe "ROR_SINGLE" @@ -107,7 +107,7 @@ class RemoteClusterAuditingToolsSuite // This test suite does not execute on Windows: there is currently no Windows version of ToxiproxyContainer ignoreOnWindows { "Should report audit events in round-robin mode, even when some nodes are unreachable" in { - rorApiManager.updateRorInIndexConfig(baseRorConfig).forceOKStatusOrConfigAlreadyLoaded() + rorApiManager.updateRorInIndexSettings(baseRorSettingsYaml).forceOKStatusOrSettingsAlreadyLoaded() val auditNode1 = proxiedContainers(0) val auditNode2 = proxiedContainers(1) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/BaseAdminApiSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/BaseAdminApiSuite.scala index cb0f5a8f58..6b0e25e855 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/BaseAdminApiSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/BaseAdminApiSuite.scala @@ -49,37 +49,37 @@ trait BaseAdminApiSuite protected def readonlyrestIndexName: String - protected def rorWithIndexConfig: EsClusterContainer + protected def rorWithIndexSettings: EsClusterContainer - protected def rorWithNoIndexConfig: EsClusterContainer + protected def rorWithNoIndexSettings: EsClusterContainer - private lazy val ror1_1Node = rorWithIndexConfig.nodes.head - private lazy val ror1_2Node = rorWithIndexConfig.nodes.tail.head - private lazy val ror2_1Node = rorWithNoIndexConfig.nodes.head + private lazy val ror1_1Node = rorWithIndexSettings.nodes.head + private lazy val ror1_2Node = rorWithIndexSettings.nodes.tail.head + private lazy val ror2_1Node = rorWithNoIndexSettings.nodes.head - private lazy val ror1WithIndexConfigAdminActionManager = new RorApiManager(clients.head.adminClient, esVersionUsed) - private lazy val rorWithNoIndexConfigAdminActionManager = new RorApiManager(clients.last.adminClient, esVersionUsed) + private lazy val ror1WithIndexSettingsAdminActionManager = new RorApiManager(clients.head.adminClient, esVersionUsed) + private lazy val rorWithNoIndexSettingsAdminActionManager = new RorApiManager(clients.last.adminClient, esVersionUsed) private lazy val adminSearchManager = new SearchManager(clients.head.basicAuthClient("admin", "container"), esVersionUsed) private lazy val adminIndexManager = new IndexManager(clients.head.basicAuthClient("admin", "container"), esVersionUsed) - private val testConfigEsDocumentId = "2" + private val testSettingsEsDocumentId = "2" protected val settingsReloadInterval: FiniteDuration = 5 seconds override lazy val esTargets = NonEmptyList.of(ror1_1Node, ror1_2Node, ror2_1Node) - override lazy val clusterContainers = NonEmptyList.of(rorWithIndexConfig, rorWithNoIndexConfig) + override lazy val clusterContainers = NonEmptyList.of(rorWithIndexSettings, rorWithNoIndexSettings) "An admin REST API" should { - "provide a method for force refresh ROR config" which { + "provide a method for force refresh ROR settings" which { "is going to reload ROR core" when { - "in-index config is newer than current one" in { - rorWithNoIndexConfigAdminActionManager - .insertInIndexConfigDirectlyToRorIndex( - rorConfigIndex = readonlyrestIndexName, - config = getResourceContent("/admin_api/readonlyrest_index.yml") + "in-index settings is newer than current one" in { + rorWithNoIndexSettingsAdminActionManager + .insertInIndexSettingsDirectlyToRorIndex( + rorIndex = readonlyrestIndexName, + settings = getResourceContent("/admin_api/readonlyrest_index.yml") ) .force() - val result = rorWithNoIndexConfigAdminActionManager.reloadRorConfig() + val result = rorWithNoIndexSettingsAdminActionManager.reloadRorSettings() result should have statusCode 200 result.responseJson should be(ujson.read( """ @@ -91,16 +91,16 @@ trait BaseAdminApiSuite )) } } - "return info that config is up to date" when { - "in-index config is the same as current one" in { - rorWithNoIndexConfigAdminActionManager - .insertInIndexConfigDirectlyToRorIndex( - rorConfigIndex = readonlyrestIndexName, - config = getResourceContent("/admin_api/readonlyrest.yml") + "return info that settings are up to date" when { + "in-index settings are the same as current one" in { + rorWithNoIndexSettingsAdminActionManager + .insertInIndexSettingsDirectlyToRorIndex( + rorIndex = readonlyrestIndexName, + settings = getResourceContent("/admin_api/readonlyrest.yml") ) .force() - val result = rorWithNoIndexConfigAdminActionManager.reloadRorConfig() + val result = rorWithNoIndexSettingsAdminActionManager.reloadRorSettings() result should have statusCode 200 result.responseJson should be(ujson.read( """ @@ -112,30 +112,30 @@ trait BaseAdminApiSuite )) } } - "return info that in-index config does not exist" when { + "return info that in-index settings do not exist" when { "there is no in-index settings configured yet" in { - val result = rorWithNoIndexConfigAdminActionManager.reloadRorConfig() + val result = rorWithNoIndexSettingsAdminActionManager.reloadRorSettings() result should have statusCode 200 result.responseJson should be(ujson.read( s""" |{ | "status": "ko", - | "message": "Cannot find settings index" + | "message": "Cannot find ReadonlyREST settings index" |} |""".stripMargin )) } } - "return info that cannot reload config" when { - "config cannot be reloaded (eg. because LDAP is not achievable)" in { - rorWithNoIndexConfigAdminActionManager - .insertInIndexConfigDirectlyToRorIndex( - rorConfigIndex = readonlyrestIndexName, - config = getResourceContent("/admin_api/readonlyrest_with_ldap.yml") + "return info that cannot reload settings" when { + "settings cannot be reloaded (eg. because LDAP is not achievable)" in { + rorWithNoIndexSettingsAdminActionManager + .insertInIndexSettingsDirectlyToRorIndex( + rorIndex = readonlyrestIndexName, + settings = getResourceContent("/admin_api/readonlyrest_with_ldap.yml") ) .force() - val result = rorWithNoIndexConfigAdminActionManager.reloadRorConfig() + val result = rorWithNoIndexSettingsAdminActionManager.reloadRorSettings() result should have statusCode 200 result.responseJson should be(ujson.read( """ @@ -148,11 +148,11 @@ trait BaseAdminApiSuite } } } - "provide a method for update in-index config" which { - "is going to reload ROR core and store new in-index config" when { - "configuration is new and correct" in { + "provide a method for update in-index settings" which { + "is going to reload ROR core and store new in-index settings" when { + "settings are new and correct" in { def forceReload(rorSettingsResource: String) = { - val result = ror1WithIndexConfigAdminActionManager.updateRorInIndexConfig(getResourceContent(rorSettingsResource)) + val result = ror1WithIndexSettingsAdminActionManager.updateRorInIndexSettings(getResourceContent(rorSettingsResource)) result should have statusCode 200 result.responseJson should be(ujson.read( """ @@ -185,7 +185,7 @@ trait BaseAdminApiSuite forceReload("/admin_api/readonlyrest_first_update.yml") // after first reload only dev1 can access indices - Thread.sleep(settingsReloadInterval.plus(10 second).toMillis) // have to wait for ROR1_2 instance config reload + Thread.sleep(settingsReloadInterval.plus(10 second).toMillis) // have to wait for ROR1_2 instance settings reload val dev1ror1After1stReloadResults = dev1Ror1stInstanceSearchManager.search("test1_index") dev1ror1After1stReloadResults should have statusCode 200 val dev2ror1After1stReloadResults = dev2Ror1stInstanceSearchManager.search("test2_index") @@ -199,7 +199,7 @@ trait BaseAdminApiSuite forceReload("/admin_api/readonlyrest_second_update.yml") // after second reload dev1 & dev2 can access indices - Thread.sleep(settingsReloadInterval.plus(10 second).toMillis) // have to wait for ROR1_2 instance config reload + Thread.sleep(settingsReloadInterval.plus(10 second).toMillis) // have to wait for ROR1_2 instance settings reload val dev1ror1After2ndReloadResults = dev1Ror1stInstanceSearchManager.search("test1_index") dev1ror1After2ndReloadResults should have statusCode 200 val dev2ror1After2ndReloadResults = dev2Ror1stInstanceSearchManager.search("test2_index") @@ -211,10 +211,10 @@ trait BaseAdminApiSuite } } - "return info that config is up to date" when { - "in-index config is the same as provided one" in { - val result = ror1WithIndexConfigAdminActionManager - .updateRorInIndexConfig(getResourceContent("/admin_api/readonlyrest_index.yml")) + "return info that settings are up to date" when { + "in-index settings are the same as provided one" in { + val result = ror1WithIndexSettingsAdminActionManager + .updateRorInIndexSettings(getResourceContent("/admin_api/readonlyrest_index.yml")) assertSettingsInIndex(getResourceContent("/admin_api/readonlyrest_index.yml")) result should have statusCode 200 @@ -228,10 +228,10 @@ trait BaseAdminApiSuite )) } } - "return info that config is malformed" when { + "return info that settings are malformed" when { "invalid YAML is provided" in { - val result = ror1WithIndexConfigAdminActionManager - .updateRorInIndexConfig(getResourceContent("/admin_api/readonlyrest_malformed.yml")) + val result = ror1WithIndexSettingsAdminActionManager + .updateRorInIndexSettings(getResourceContent("/admin_api/readonlyrest_malformed.yml")) result should have statusCode 200 result.responseJson("status").str should be("ko") @@ -240,8 +240,8 @@ trait BaseAdminApiSuite } "return info that request is malformed" when { "settings key missing" in { - val result = ror1WithIndexConfigAdminActionManager - .updateRorInIndexConfigRaw(rawRequestBody = "{}") + val result = ror1WithIndexSettingsAdminActionManager + .updateRorInIndexSettingsRaw(rawRequestBody = "{}") result should have statusCode 400 result.responseJson should be(ujson.read( @@ -256,8 +256,8 @@ trait BaseAdminApiSuite } "return info that cannot reload" when { "ROR core cannot be reloaded" in { - val result = ror1WithIndexConfigAdminActionManager - .updateRorInIndexConfig(getResourceContent("/admin_api/readonlyrest_with_ldap.yml")) + val result = ror1WithIndexSettingsAdminActionManager + .updateRorInIndexSettings(getResourceContent("/admin_api/readonlyrest_with_ldap.yml")) result should have statusCode 200 result.responseJson should be(ujson.read( @@ -271,11 +271,11 @@ trait BaseAdminApiSuite } } } - "provide a method for fetching current in-index config" which { - "return current config" when { + "provide a method for fetching current in-index settings" which { + "return current settings" when { "there is one in index" in { - val result = ror1WithIndexConfigAdminActionManager - .updateRorInIndexConfig(getResourceContent("/admin_api/readonlyrest_first_update.yml")) + val result = ror1WithIndexSettingsAdminActionManager + .updateRorInIndexSettings(getResourceContent("/admin_api/readonlyrest_first_update.yml")) result should have statusCode 200 result.responseJson should be(ujson.read( @@ -289,21 +289,21 @@ trait BaseAdminApiSuite assertSettingsInIndex(getResourceContent("/admin_api/readonlyrest_first_update.yml")) - val getIndexConfigResult = ror1WithIndexConfigAdminActionManager.getRorInIndexConfig + val getIndexSettingsResult = ror1WithIndexSettingsAdminActionManager.getRorInIndexSettings result should have statusCode 200 - getIndexConfigResult.responseJson("status").str should be("ok") - getIndexConfigResult.responseJson("message").str should be { + getIndexSettingsResult.responseJson("status").str should be("ok") + getIndexSettingsResult.responseJson("message").str should be { getResourceContent("/admin_api/readonlyrest_first_update.yml") } } } - "return info that there is no in-index config" when { + "return info that there is no in-index settings" when { "there is no index" in { - assertNoRorConfigInIndex(rorWithNoIndexConfigAdminActionManager) + assertNoRorSettingsInIndex(rorWithNoIndexSettingsAdminActionManager) } - "there is no config document in index" in { - val result = ror1WithIndexConfigAdminActionManager - .updateRorInIndexConfig(getResourceContent("/admin_api/readonlyrest_first_update.yml")) + "there are no settings document in index" in { + val result = ror1WithIndexSettingsAdminActionManager + .updateRorInIndexSettings(getResourceContent("/admin_api/readonlyrest_first_update.yml")) result should have statusCode 200 result.responseJson should be(ujson.read( @@ -320,13 +320,13 @@ trait BaseAdminApiSuite val deleteResponse = adminDocumentManager.deleteByQuery(readonlyrestIndexName, matchAllQuery) deleteResponse should have statusCode 200 - assertNoRorConfigInIndex(rorWithNoIndexConfigAdminActionManager) + assertNoRorSettingsInIndex(rorWithNoIndexSettingsAdminActionManager) } } } - "provide a method for fetching current file config" which { - "return current config" in { - val result = ror1WithIndexConfigAdminActionManager.getRorFileConfig + "provide a method for fetching current file settings" which { + "return current settings" in { + val result = ror1WithIndexSettingsAdminActionManager.getRorFileSettings result should have statusCode 200 result.responseJson("status").str should be("ok") result.responseJson("message").str should be(getResourceContent("/admin_api/readonlyrest.yml")) @@ -355,20 +355,20 @@ trait BaseAdminApiSuite } } "return current test settings" when { - "should invalidate configuration when index removed" in { + "should invalidate settings when index removed" in { def forceReload(rorSettingsResource: String): Unit = { - val testConfig = getResourceContent(rorSettingsResource) - val configTtl = 30.minutes - updateRorTestConfig(rorClients.head, testConfig, configTtl) + val testSettings = getResourceContent(rorSettingsResource) + val settingsTtl = 30.minutes + updateRorTestSettings(rorClients.head, testSettings, settingsTtl) - // check if config is present in index + // check if settings are present in index assertTestSettingsInIndex( - expectedConfig = testConfig, - expectedTtl = 30 minutes + expectedSettings = testSettings, + expectedTtl = settingsTtl ) - eventually { // await until all nodes load config - rorClients.foreach(assertTestSettingsPresent(_, testConfig, "30 minutes")) + eventually { // await until all nodes load settings + rorClients.foreach(assertTestSettingsPresent(_, testSettings, "30 minutes")) } } @@ -387,46 +387,46 @@ trait BaseAdminApiSuite } // before first reload no user can access indices - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) - // reload config + // reload settings forceReload("/admin_api/readonlyrest_first_update_with_impersonation.yml") // check if impersonation works - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test1_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test1_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test2_index")) - // drop index containing ror config + // drop index containing ror settings val indexManager = new IndexManager(clients.head.basicAuthClient("admin", "container"), esVersionUsed) indexManager.removeIndex(readonlyrestIndexName).force() - eventually { // await until all nodes invalidate the config + eventually { // await until all nodes invalidate the settings rorClients.foreach { assertTestSettingsNotConfigured } } // check if impersonation is not configured - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) } - "configuration is valid and response without warnings" in { + "settings are valid and response without warnings" in { def forceReload(rorSettingsResource: String): Unit = { - val testConfig = getResourceContent(rorSettingsResource) - updateRorTestConfig(rorClients.head, testConfig, 30 minutes) + val testSettingsYaml = getResourceContent(rorSettingsResource) + updateRorTestSettings(rorClients.head, testSettingsYaml, 30 minutes) - assertTestSettingsInIndex(testConfig, 30 minutes) + assertTestSettingsInIndex(testSettingsYaml, 30 minutes) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => - assertTestSettingsPresent(rorApiManager, testConfig, "30 minutes") + assertTestSettingsPresent(rorApiManager, testSettingsYaml, "30 minutes") } } } @@ -446,37 +446,37 @@ trait BaseAdminApiSuite } // before first reload no user can access indices - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) // first reload forceReload("/admin_api/readonlyrest_first_update_with_impersonation.yml") // after first reload only dev1 can access indices - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test1_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test1_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test2_index")) // second reload forceReload("/admin_api/readonlyrest_second_update_with_impersonation.yml") // after second reload dev1 & dev2 can access indices - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(indexNotFound(_, "test1_index")) - dev2SearchManagers.foreach(allowedSearch(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertIndexNotFound(_, "test1_index")) + dev2SearchManagers.foreach(assertAllowedSearch(_, "test2_index")) } - "configuration is valid and response with warnings" in { + "settings are valid and response with warnings" in { val warningsJson = ujson.read( """ |[ | { | "block_name": "test1", | "rule_name": "auth_key_sha256", - | "message": "The rule contains fully hashed username and password. It doesn't support impersonation in this configuration", + | "message": "The rule contains fully hashed username and password. It doesn't support impersonation in this use case.", | "hint": "You can use second version of the rule and use not hashed username. Like that: `auth_key_sha256: USER_NAME:hash(PASSWORD)" | } |] @@ -484,18 +484,18 @@ trait BaseAdminApiSuite ) def forceReload(rorSettingsResource: String, warnings: ujson.Value): Unit = { - val testConfig = getResourceContent(rorSettingsResource) - updateRorTestConfig(rorClients.head, testConfig, 30 minutes, warnings) + val testSettingsYaml = getResourceContent(rorSettingsResource) + updateRorTestSettings(rorClients.head, testSettingsYaml, 30 minutes, warnings) - // check if config is present in index + // check if settings are present in index assertTestSettingsInIndex( - expectedConfig = testConfig, + expectedSettings = testSettingsYaml, expectedTtl = 30 minutes ) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => - val response = rorApiManager.currentRorTestConfig + val response = rorApiManager.currentRorTestSettings response should have statusCode 200 response.responseJson("status").str should be("TEST_SETTINGS_PRESENT") } @@ -510,16 +510,16 @@ trait BaseAdminApiSuite } // before reload no user can access indices - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) val rorSettingsResource = "/admin_api/readonlyrest_with_warnings.yml" forceReload(rorSettingsResource, warningsJson) - val testConfig = getResourceContent(rorSettingsResource) + val testSettings = getResourceContent(rorSettingsResource) rorClients.foreach { rorApiManager => assertTestSettingsPresent( rorApiManager, - testConfig, + testSettings, "30 minutes", warningsJson ) @@ -527,18 +527,18 @@ trait BaseAdminApiSuite // user with hashed credential cannot be impersonated dev1SearchManagers.foreach { - impersonationNotAllowed(_, "test1_index") + assertImpersonationNotAllowed(_, "test1_index") } } "return local users" in { - val testConfig = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml") - updateRorTestConfig(rorClients.head, testConfig, 30 minutes) + val testSettingsYaml = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml") + updateRorTestSettings(rorClients.head, testSettingsYaml, 30 minutes) - assertTestSettingsInIndex(expectedConfig = testConfig, expectedTtl = 30 minutes) + assertTestSettingsInIndex(expectedSettings = testSettingsYaml, expectedTtl = 30 minutes) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { - assertTestSettingsPresent(_, testConfig, "30 minutes") + assertTestSettingsPresent(_, testSettingsYaml, "30 minutes") } } @@ -551,16 +551,16 @@ trait BaseAdminApiSuite } } } - "return info that configuration was invalidated" in { + "return info that settings were invalidated" in { def forceReload(rorSettingsResource: String): Unit = { - val testConfig = getResourceContent(rorSettingsResource) - updateRorTestConfig(rorClients.head, testConfig, 30 minutes) + val testSettings = getResourceContent(rorSettingsResource) + updateRorTestSettings(rorClients.head, testSettings, 30 minutes) - assertTestSettingsInIndex(expectedConfig = testConfig, expectedTtl = 30 minutes) + assertTestSettingsInIndex(expectedSettings = testSettings, expectedTtl = 30 minutes) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => - assertTestSettingsPresent(rorApiManager, testConfig, "30 minutes") + assertTestSettingsPresent(rorApiManager, testSettings, "30 minutes") } } } @@ -580,35 +580,35 @@ trait BaseAdminApiSuite } // before first reload no user can access indices - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) // first reload forceReload("/admin_api/readonlyrest_first_update_with_impersonation.yml") // after first reload only dev1 can access indices - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test1_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test1_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test2_index")) // second reload val rorSettingsResource = "/admin_api/readonlyrest_second_update_with_impersonation.yml" forceReload(rorSettingsResource) // after second reload dev1 & dev2 can access indices - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(indexNotFound(_, "test1_index")) - dev2SearchManagers.foreach(allowedSearch(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertIndexNotFound(_, "test1_index")) + dev2SearchManagers.foreach(assertAllowedSearch(_, "test2_index")) - invalidateRorTestConfig(rorClients.head) + invalidateRorTestSettings(rorClients.head) eventually { rorClients.foreach { rorApiManager => - val response = rorApiManager.currentRorTestConfig + val response = rorApiManager.currentRorTestSettings response should have statusCode 200 response.responseJson("status").str should be("TEST_SETTINGS_INVALIDATED") response.responseJson("message").str should be("ROR Test settings are invalidated") @@ -618,29 +618,29 @@ trait BaseAdminApiSuite } // after test core invalidation, impersonations requests should be rejected - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) } } - "provide a method for reload test config engine" which { + "provide a method for reload test settings engine" which { "is going to reload ROR test core with TTL" when { - "configuration is new and correct" in { - def forceReload(rorSettingsResource: String, configTtl: FiniteDuration, configTtlString: String): Unit = { - val testConfig = getResourceContent(rorSettingsResource) - updateRorTestConfig(rorClients.head, testConfig, configTtl) + "settings are new and correct" in { + def forceReload(rorSettingsResource: String, settingsTtl: FiniteDuration, settingsTtlString: String): Unit = { + val testSettings = getResourceContent(rorSettingsResource) + updateRorTestSettings(rorClients.head, testSettings, settingsTtl) - assertTestSettingsInIndex(expectedConfig = testConfig, expectedTtl = configTtl) + assertTestSettingsInIndex(expectedSettings = testSettings, expectedTtl = settingsTtl) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => - assertTestSettingsPresent(rorApiManager, testConfig, configTtlString) + assertTestSettingsPresent(rorApiManager, testSettings, settingsTtlString) } } } - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => assertTestSettingsNotConfigured(rorApiManager) } @@ -661,85 +661,85 @@ trait BaseAdminApiSuite } // before first reload no user can access indices - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) // first reload forceReload( rorSettingsResource = "/admin_api/readonlyrest_first_update_with_impersonation.yml", - configTtl = 30 minutes, - configTtlString = "30 minutes" + settingsTtl = 30 minutes, + settingsTtlString = "30 minutes" ) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => assertTestSettingsPresent( rorApiManager = rorApiManager, - testConfig = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml"), + testSettings = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml"), expectedTtl = "30 minutes" ) } } // after first reload only dev1 can access indices - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test1_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test1_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test2_index")) // second reload val rorSettingsResource = "/admin_api/readonlyrest_second_update_with_impersonation.yml" - val configTtl = 15.seconds + val settingsTtl = 15.seconds forceReload( rorSettingsResource = rorSettingsResource, - configTtl = configTtl, - configTtlString = "15 seconds" + settingsTtl = settingsTtl, + settingsTtlString = "15 seconds" ) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => assertTestSettingsPresent( rorApiManager = rorApiManager, - testConfig = getResourceContent(rorSettingsResource), + testSettings = getResourceContent(rorSettingsResource), expectedTtl = "15 seconds" ) } } // after second reload dev1 & dev2 can access indices - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(indexNotFound(_, "test1_index")) - dev2SearchManagers.foreach(allowedSearch(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertIndexNotFound(_, "test1_index")) + dev2SearchManagers.foreach(assertAllowedSearch(_, "test2_index")) // wait for test engine auto-destruction - Thread.sleep(configTtl.toMillis) + Thread.sleep(settingsTtl.toMillis) rorClients.foreach { rorApiManager => assertTestSettingsInvalidated(rorApiManager, getResourceContent(rorSettingsResource), "15 seconds") } - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) } - "configuration is up to date and new ttl is passed" in { - val testConfig = getResourceContent("/admin_api/readonlyrest_index.yml") - updateRorTestConfig(rorClients.head, testConfig, 30 minutes) - assertTestSettingsInIndex(expectedConfig = testConfig, expectedTtl = 30 minutes) + "settings are up to date and new ttl is passed" in { + val testSettings = getResourceContent("/admin_api/readonlyrest_index.yml") + updateRorTestSettings(rorClients.head, testSettings, 30 minutes) + assertTestSettingsInIndex(expectedSettings = testSettings, expectedTtl = 30 minutes) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { - assertTestSettingsPresent(_, testConfig, "30 minutes") + assertTestSettingsPresent(_, testSettings, "30 minutes") } } val timestamps = rorClients - .map(_.currentRorTestConfig.responseJson("valid_to").str) + .map(_.currentRorTestSettings.responseJson("valid_to").str) .map(Instant.parse).toSet timestamps.size should be(1) @@ -747,18 +747,18 @@ trait BaseAdminApiSuite // wait for valid_to comparison purpose Thread.sleep(100) - updateRorTestConfig(rorClients.head, testConfig, 45 minutes) - assertTestSettingsInIndex(expectedConfig = testConfig, expectedTtl = 45 minutes) + updateRorTestSettings(rorClients.head, testSettings, 45 minutes) + assertTestSettingsInIndex(expectedSettings = testSettings, expectedTtl = 45 minutes) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { - assertTestSettingsPresent(_, testConfig, "45 minutes") + assertTestSettingsPresent(_, testSettings, "45 minutes") } } val timestampsAfterReload = rorClients - .map(_.currentRorTestConfig.responseJson("valid_to").str) + .map(_.currentRorTestSettings.responseJson("valid_to").str) .map(Instant.parse) .toSet timestampsAfterReload.size should be(1) @@ -768,12 +768,12 @@ trait BaseAdminApiSuite } "return info that request is malformed" when { "ttl missing" in { - val config = getResourceContent("/admin_api/readonlyrest_index.yml") - val requestBody = s"""{"settings": "${escapeJava(config)}"}""" + val settings = getResourceContent("/admin_api/readonlyrest_index.yml") + val requestBody = s"""{"settings": "${escapeJava(settings)}"}""" rorClients.foreach { rorApiManager => val result = rorApiManager - .updateRorTestConfigRaw(rawRequestBody = requestBody) + .updateRorTestSettingsRaw(rawRequestBody = requestBody) result should have statusCode 400 result.responseJson should be(ujson.read( @@ -790,7 +790,7 @@ trait BaseAdminApiSuite val requestBody = s"""{"ttl": "30 m"}""" rorClients.foreach { rorApiManager => val result = rorApiManager - .updateRorTestConfigRaw(rawRequestBody = requestBody) + .updateRorTestSettingsRaw(rawRequestBody = requestBody) result should have statusCode 400 result.responseJson should be(ujson.read( @@ -804,12 +804,12 @@ trait BaseAdminApiSuite } } "ttl value in invalid format" in { - val config = getResourceContent("/admin_api/readonlyrest_index.yml") - val requestBody = s"""{"settings": "${escapeJava(config)}", "ttl": "30 units"}""" + val settings = getResourceContent("/admin_api/readonlyrest_index.yml") + val requestBody = s"""{"settings": "${escapeJava(settings)}", "ttl": "30 units"}""" rorClients.foreach { rorApiManager => val result = rorApiManager - .updateRorTestConfigRaw(rawRequestBody = requestBody) + .updateRorTestSettingsRaw(rawRequestBody = requestBody) result should have statusCode 400 result.responseJson should be(ujson.read( @@ -823,11 +823,11 @@ trait BaseAdminApiSuite } } } - "return info that config is malformed" when { + "return info that settings are malformed" when { "invalid YAML is provided" in { rorClients.foreach { rorApiManager => val result = rorApiManager - .updateRorTestConfig(getResourceContent("/admin_api/readonlyrest_malformed.yml")) + .updateRorTestSettings(getResourceContent("/admin_api/readonlyrest_malformed.yml")) result should have statusCode 200 result.responseJson("status").str should be("FAILED") @@ -839,7 +839,7 @@ trait BaseAdminApiSuite "ROR core cannot be reloaded" in { rorClients.foreach { rorApiManager => val result = rorApiManager - .updateRorTestConfig(getResourceContent("/admin_api/readonlyrest_with_ldap.yml")) + .updateRorTestSettings(getResourceContent("/admin_api/readonlyrest_with_ldap.yml")) result should have statusCode 200 result.responseJson should be(ujson.read( @@ -854,21 +854,21 @@ trait BaseAdminApiSuite } } } - "provide a method for test config engine invalidation" which { + "provide a method for test settings engine invalidation" which { "will destruct the engine on demand" in { def forceReload(rorSettingsResource: String): Unit = { - val testConfig = getResourceContent(rorSettingsResource) + val testSettings = getResourceContent(rorSettingsResource) - updateRorTestConfig(rorClients.head, testConfig, 30 minutes) + updateRorTestSettings(rorClients.head, testSettings, 30 minutes) assertTestSettingsInIndex( - expectedConfig = testConfig, + expectedSettings = testSettings, expectedTtl = 30 minutes ) - eventually { // await until all nodes load config + eventually { // await until all nodes load settings rorClients.foreach { rorApiManager => - assertTestSettingsPresent(rorApiManager, testConfig, "30 minutes") + assertTestSettingsPresent(rorApiManager, testSettings, "30 minutes") } } } @@ -887,30 +887,30 @@ trait BaseAdminApiSuite ) } - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) // first reload forceReload("/admin_api/readonlyrest_first_update_with_impersonation.yml") // after first reload only dev1 can access indices - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test1_index")) - dev2SearchManagers.foreach(operationNotAllowed(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test1_index")) + dev2SearchManagers.foreach(assertOperationNotAllowed(_, "test2_index")) // second reload forceReload("/admin_api/readonlyrest_second_update_with_impersonation.yml") // after second reload dev1 & dev2 can access indices - dev1SearchManagers.foreach(allowedSearch(_, "test1_index")) - dev1SearchManagers.foreach(indexNotFound(_, "test2_index")) - dev2SearchManagers.foreach(indexNotFound(_, "test1_index")) - dev2SearchManagers.foreach(allowedSearch(_, "test2_index")) + dev1SearchManagers.foreach(assertAllowedSearch(_, "test1_index")) + dev1SearchManagers.foreach(assertIndexNotFound(_, "test2_index")) + dev2SearchManagers.foreach(assertIndexNotFound(_, "test1_index")) + dev2SearchManagers.foreach(assertAllowedSearch(_, "test2_index")) - invalidateRorTestConfig(rorClients.head) + invalidateRorTestSettings(rorClients.head) Thread.sleep(settingsReloadInterval.toMillis) // wait for engines reload @@ -918,33 +918,33 @@ trait BaseAdminApiSuite rorClients.foreach { rorApiManager => assertTestSettingsInvalidated( rorApiManager = rorApiManager, - testConfig = getResourceContent("/admin_api/readonlyrest_second_update_with_impersonation.yml"), + testSettingsYaml = getResourceContent("/admin_api/readonlyrest_second_update_with_impersonation.yml"), expectedTtl = "30 minutes" ) } // after test core invalidation, impersonations requests should be rejected - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev1SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test1_index")) - dev2SearchManagers.foreach(testSettingsNotConfigured(_, "test2_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev1SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test1_index")) + dev2SearchManagers.foreach(assertTestSettingsNotConfigured(_, "test2_index")) } } - "main ROR config and test ROR config coexistence check" when { - "get main ROR index config" should { - "return no index config" when { - "no main and test config in the index" in { + "main ROR settings and test ROR settings coexistence check" when { + "get main ROR index settings" should { + "return no index settings" when { + "no main and test settings in the index" in { adminIndexManager.removeIndex(readonlyrestIndexName) rorClients.foreach { rorApiManager => - assertNoRorConfigInIndex(rorApiManager) + assertNoRorSettingsInIndex(rorApiManager) assertTestSettingsNotConfigured(rorApiManager) } } - "only test config in the index" in { - def forceReloadTestSettings(testConfig: String): Unit = { - updateRorTestConfig(rorClients.head, testConfig, 30 minutes) + "only test settings in the index" in { + def forceReloadTestSettings(testSettings: String): Unit = { + updateRorTestSettings(rorClients.head, testSettings, 30 minutes) assertTestSettingsInIndex( - expectedConfig = testConfig, + expectedSettings = testSettings, expectedTtl = 30 minutes ) } @@ -952,56 +952,56 @@ trait BaseAdminApiSuite adminIndexManager.removeIndex(readonlyrestIndexName) rorClients.foreach { rorApiManager => - assertNoRorConfigInIndex(rorApiManager) + assertNoRorSettingsInIndex(rorApiManager) assertTestSettingsNotConfigured(rorApiManager) } - val config = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml") - forceReloadTestSettings(config) + val settings = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml") + forceReloadTestSettings(settings) Thread.sleep(settingsReloadInterval.toMillis) // wait for engines reload rorClients.foreach { rorApiManager => - assertNoRorConfigInIndex(rorApiManager) + assertNoDocumentWithMainRorSettingsInIndex(rorApiManager) assertTestSettingsPresent( rorApiManager, - testConfig = config, + testSettings = settings, expectedTtl = "30 minutes" ) } } } - "return index config" when { - "only main config in the index" in { - def forceReloadMainSettings(config: String) = { - updateRorMainConfig(rorClients.head, config) - assertSettingsInIndex(expectedConfig = config) + "return index settings" when { + "only main settings in the index" in { + def forceReloadMainSettings(mainSettingsYaml: String) = { + updateRorMainSettings(rorClients.head, mainSettingsYaml) + assertSettingsInIndex(expectedSettings = mainSettingsYaml) } adminIndexManager.removeIndex(readonlyrestIndexName) rorClients.foreach { rorApiManager => - assertNoRorConfigInIndex(rorApiManager) + assertNoRorSettingsInIndex(rorApiManager) assertTestSettingsNotConfigured(rorApiManager) } - val config = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml") - forceReloadMainSettings(config) + val settings = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml") + forceReloadMainSettings(settings) rorClients.foreach { rorApiManager => - assertInIndexConfigPresent(rorApiManager, config) + assertInIndexSettingsPresent(rorApiManager, settings) assertTestSettingsNotConfigured(rorApiManager) } } - "main and test configs in the index" in { - def forceReloadMainSettings(config: String) = { - updateRorMainConfig(rorClients.head, config) - assertSettingsInIndex(expectedConfig = config) + "main and test settings in the index" in { + def forceReloadMainSettings(settings: String) = { + updateRorMainSettings(rorClients.head, settings) + assertSettingsInIndex(expectedSettings = settings) } - def forceReloadTestSettings(testConfig: String): Unit = { - updateRorTestConfig(rorClients.head, testConfig, 30 minutes) + def forceReloadTestSettings(testSettings: String): Unit = { + updateRorTestSettings(rorClients.head, testSettings, 30 minutes) assertTestSettingsInIndex( - expectedConfig = testConfig, + expectedSettings = testSettings, expectedTtl = 30 minutes ) } @@ -1009,18 +1009,18 @@ trait BaseAdminApiSuite adminIndexManager.removeIndex(readonlyrestIndexName) rorClients.foreach { rorApiManager => - assertNoRorConfigInIndex(rorApiManager) + assertNoRorSettingsInIndex(rorApiManager) assertTestSettingsNotConfigured(rorApiManager) } - val config = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml") - forceReloadMainSettings(config) - forceReloadTestSettings(config) + val settings = getResourceContent("/admin_api/readonlyrest_first_update_with_impersonation.yml") + forceReloadMainSettings(settings) + forceReloadTestSettings(settings) Thread.sleep(settingsReloadInterval.toMillis) // wait for engines reload rorClients.foreach { client => - assertInIndexConfigPresent(client, config = config) - assertTestSettingsPresent(client, testConfig = config, expectedTtl = "30 minutes") + assertInIndexSettingsPresent(client, settings) + assertTestSettingsPresent(client, testSettings = settings, expectedTtl = "30 minutes") } } } @@ -1029,20 +1029,20 @@ trait BaseAdminApiSuite } override protected def beforeEach(): Unit = { - // back to configuration loaded on container start - rorWithNoIndexConfigAdminActionManager - .updateRorInIndexConfig(getResourceContent("/admin_api/readonlyrest.yml")) + // back to settings loaded on container start + rorWithNoIndexSettingsAdminActionManager + .updateRorInIndexSettings(getResourceContent("/admin_api/readonlyrest.yml")) .force() new IndexManager(ror2_1Node.adminClient, esVersionUsed).removeIndex(readonlyrestIndexName) adminIndexManager.removeIndex(readonlyrestIndexName) - ror1WithIndexConfigAdminActionManager - .updateRorInIndexConfig(getResourceContent("/admin_api/readonlyrest_index.yml")) + ror1WithIndexSettingsAdminActionManager + .updateRorInIndexSettings(getResourceContent("/admin_api/readonlyrest_index.yml")) .force() - eventually { // await until all nodes invalidate the config + eventually { // await until all nodes invalidate the settings rorClients.foreach(assertTestSettingsNotConfigured) } } @@ -1062,7 +1062,7 @@ trait BaseAdminApiSuite } } - private def assertSettingsInIndex(expectedConfig: String) = { + private def assertSettingsInIndex(expectedSettings: String) = { val indexSearchResponse = adminSearchManager.search(readonlyrestIndexName) indexSearchResponse should have statusCode 200 val indexSearchHits = indexSearchResponse.responseJson("hits")("hits").arr.toList @@ -1072,50 +1072,66 @@ trait BaseAdminApiSuite }.value val testSettingsDocumentContent = testSettingsDocumentHit("_source") - testSettingsDocumentContent("settings").str should be(expectedConfig) + testSettingsDocumentContent("settings").str should be(expectedSettings) } - private def assertTestSettingsInIndex(expectedConfig: String, expectedTtl: FiniteDuration) = { + private def assertTestSettingsInIndex(expectedSettings: String, expectedTtl: FiniteDuration) = { val indexSearchResponse = adminSearchManager.search(readonlyrestIndexName) indexSearchResponse should have statusCode 200 val indexSearchHits = indexSearchResponse.responseJson("hits")("hits").arr.toList indexSearchHits.size should be >= 1 // at least main document or test document should be present - val testSettingsDocumentHit = indexSearchHits.find { searchResult => - (searchResult("_index").str, searchResult("_id").str) === (readonlyrestIndexName, testConfigEsDocumentId) - }.value + val testSettingsDocumentHit = indexSearchHits + .find { searchResult => + (searchResult("_index").str, searchResult("_id").str) === (readonlyrestIndexName, testSettingsEsDocumentId) + } + .value val testSettingsDocumentContent = testSettingsDocumentHit("_source") - testSettingsDocumentContent("settings").str should be(expectedConfig) + testSettingsDocumentContent("settings").str should be(expectedSettings) testSettingsDocumentContent("expiration_ttl_millis").str should be(expectedTtl.toMillis.toString) testSettingsDocumentContent("expiration_timestamp").str.isInIsoDateTimeFormat should be(true) - val mocksContent = ujson.read(testSettingsDocumentContent("auth_services_mocks").str) + + val mocksContent = testSettingsDocumentContent("auth_services_mocks") mocksContent("ldapMocks").obj.isEmpty should be(true) mocksContent("externalAuthenticationMocks").obj.isEmpty should be(true) mocksContent("externalAuthorizationMocks").obj.isEmpty should be(true) } - private def assertNoRorConfigInIndex(rorApiManager: RorApiManager) = { - val result = rorApiManager.getRorInIndexConfig + private def assertNoRorSettingsInIndex(rorApiManager: RorApiManager) = { + val result = rorApiManager.getRorInIndexSettings result should have statusCode 200 result.responseJson should be(ujson.read( """ |{ | "status": "empty", - | "message": "Cannot find settings index" + | "message": "Cannot find ReadonlyREST settings index" + |} + |""".stripMargin + )) + } + + private def assertNoDocumentWithMainRorSettingsInIndex(rorApiManager: RorApiManager) = { + val result = rorApiManager.getRorInIndexSettings + result should have statusCode 200 + result.responseJson should be(ujson.read( + """ + |{ + | "status": "ko", + | "message": "Cannot found document with ReadonlyREST settings" |} |""".stripMargin )) } - private def assertInIndexConfigPresent(rorApiManager: RorApiManager, config: String) = { - val getIndexConfigResult = rorApiManager.getRorInIndexConfig - getIndexConfigResult should have statusCode 200 - getIndexConfigResult.responseJson("status").str should be("ok") - getIndexConfigResult.responseJson("message").str should be(config) + private def assertInIndexSettingsPresent(rorApiManager: RorApiManager, settings: String) = { + val result = rorApiManager.getRorInIndexSettings + result should have statusCode 200 + result.responseJson("status").str should be("ok") + result.responseJson("message").str should be(settings) } private def assertTestSettingsNotConfigured(rorApiManager: RorApiManager) = { - val response = rorApiManager.currentRorTestConfig + val response = rorApiManager.currentRorTestSettings response should have statusCode 200 response.responseJson should be(ujson.read( """ @@ -1128,34 +1144,34 @@ trait BaseAdminApiSuite } private def assertTestSettingsPresent(rorApiManager: RorApiManager, - testConfig: String, + testSettings: String, expectedTtl: String, expectedWarningsJson: Value = ujson.read("[]")) = { - val response = rorApiManager.currentRorTestConfig + val response = rorApiManager.currentRorTestSettings response should have statusCode 200 response.responseJson("status").str should be("TEST_SETTINGS_PRESENT") - response.responseJson("settings").str should be(testConfig) + response.responseJson("settings").str should be(testSettings) response.responseJson("ttl").str should be(expectedTtl) response.responseJson("valid_to").str.isInIsoDateTimeFormat should be(true) response.responseJson("warnings") should be(expectedWarningsJson) } private def assertTestSettingsInvalidated(rorApiManager: RorApiManager, - testConfig: String, + testSettingsYaml: String, expectedTtl: String) = { - val response = rorApiManager.currentRorTestConfig + val response = rorApiManager.currentRorTestSettings response should have statusCode 200 response.responseJson("status").str should be("TEST_SETTINGS_INVALIDATED") response.responseJson("message").str should be("ROR Test settings are invalidated") - response.responseJson("settings").str should be(testConfig) + response.responseJson("settings").str should be(testSettingsYaml) response.responseJson("ttl").str should be(expectedTtl) } - private def updateRorTestConfig(rorApiManager: RorApiManager, - testConfig: String, - configTtl: FiniteDuration, - expectedWarningsJson: Value = ujson.read("[]")) = { - val response = rorApiManager.updateRorTestConfig(testConfig, configTtl) + private def updateRorTestSettings(rorApiManager: RorApiManager, + testSettingsYaml: String, + settingsTtl: FiniteDuration, + expectedWarningsJson: Value = ujson.read("[]")) = { + val response = rorApiManager.updateRorTestSettings(testSettingsYaml, settingsTtl) response should have statusCode 200 response.responseJson("status").str should be("OK") response.responseJson("message").str should be("updated settings") @@ -1163,8 +1179,8 @@ trait BaseAdminApiSuite response.responseJson("warnings") should be(expectedWarningsJson) } - private def updateRorMainConfig(rorApiManager: RorApiManager, config: String) = { - val result = rorApiManager.updateRorInIndexConfig(config) + private def updateRorMainSettings(rorApiManager: RorApiManager, mainSettingsYaml: String) = { + val result = rorApiManager.updateRorInIndexSettings(mainSettingsYaml) result should have statusCode 200 result.responseJson should be(ujson.read( """ @@ -1176,8 +1192,8 @@ trait BaseAdminApiSuite )) } - private def invalidateRorTestConfig(rorApiManager: RorApiManager) = { - val response = rorApiManager.invalidateRorTestConfig() + private def invalidateRorTestSettings(rorApiManager: RorApiManager) = { + val response = rorApiManager.invalidateRorTestSettings() response should have statusCode 200 response.responseJson should be(ujson.read( """ @@ -1189,7 +1205,7 @@ trait BaseAdminApiSuite )) } - private def testSettingsNotConfigured(sm: SearchManager, indexName: String) = { + private def assertTestSettingsNotConfigured(sm: SearchManager, indexName: String) = { val results = sm.search(indexName) results should have statusCode 403 results.responseJson should be(ujson.read( @@ -1213,12 +1229,12 @@ trait BaseAdminApiSuite )) } - private def allowedSearch(sm: SearchManager, indexName: String) = { + private def assertAllowedSearch(sm: SearchManager, indexName: String) = { val results = sm.search(indexName) results should have statusCode 200 } - private def indexNotFound(sm: SearchManager, indexName: String) = { + private def assertIndexNotFound(sm: SearchManager, indexName: String) = { val results = sm.search(indexName) results should have statusCode 404 @@ -1258,8 +1274,7 @@ trait BaseAdminApiSuite )) } - - private def operationNotAllowed(sm: SearchManager, indexName: String) = { + private def assertOperationNotAllowed(sm: SearchManager, indexName: String) = { val results = sm.search(indexName) results should have statusCode 403 results.responseJson should be(ujson.read( @@ -1282,7 +1297,7 @@ trait BaseAdminApiSuite )) } - private def impersonationNotAllowed(sm: SearchManager, indexName: String) = { + private def assertImpersonationNotAllowed(sm: SearchManager, indexName: String) = { val results = sm.search(indexName) results should have statusCode 403 results.responseJson should be(ujson.read( diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/BaseAuditingToolsSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/BaseAuditingToolsSuite.scala index 5a9b4b5586..9199532e93 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/BaseAuditingToolsSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/BaseAuditingToolsSuite.scala @@ -51,7 +51,7 @@ trait BaseAuditingToolsSuite protected def assertForEveryAuditEntry(entry: JSON): Unit - protected def baseRorConfig: String + protected def baseRorSettingsYaml: String protected def baseAuditDataStreamName: Option[String] @@ -375,7 +375,7 @@ trait BaseAuditingToolsSuite disableAudit() val newIndex = s"audit-index-${UUID.randomUUID().toString}" - rorApiManager.updateRorInIndexConfig(rorConfigWithIndexAudit(newIndex)).forceOkStatus() + rorApiManager.updateRorInIndexSettings(rorSettingsWithIndexAudit(newIndex)).forceOkStatus() val adminAuditManager = new AuditIndexManager(destNodeClientProvider.adminClient, esVersionUsed, newIndex) auditEventAssertion(adminAuditManager) @@ -392,7 +392,7 @@ trait BaseAuditingToolsSuite assertDataStreamNotExists(newDataStream) - rorApiManager.updateRorInIndexConfig(rorConfigWithDataStreamAudit(newDataStream)).forceOkStatus() + rorApiManager.updateRorInIndexSettings(rorSettingsWithDataStreamAudit(newDataStream)).forceOkStatus() eventually { val response = dataStreamManager.getAllDataStreams() @@ -427,7 +427,7 @@ trait BaseAuditingToolsSuite val indexLifecycleManager = new IndexLifecycleManager(destNodeClientProvider.adminClient, esVersionUsed) indexLifecycleManager.putPolicyAndWaitForIndexing(id = s"$newDataStream-lifecycle-policy", policy) - rorApiManager.updateRorInIndexConfig(rorConfigWithDataStreamAudit(newDataStream)).forceOkStatus() + rorApiManager.updateRorInIndexSettings(rorSettingsWithDataStreamAudit(newDataStream)).forceOkStatus() eventually { assertDataStreamExists(newDataStream) @@ -462,7 +462,7 @@ trait BaseAuditingToolsSuite val templateManager = new ComponentTemplateManager(destNodeClientProvider.adminClient, esVersionUsed) templateManager.putTemplateAndWaitForIndexing(templateName = s"$newDataStream-mappings", body = template) - rorApiManager.updateRorInIndexConfig(rorConfigWithDataStreamAudit(newDataStream)).forceOkStatus() + rorApiManager.updateRorInIndexSettings(rorSettingsWithDataStreamAudit(newDataStream)).forceOkStatus() eventually { assertDataStreamExists(newDataStream) @@ -490,7 +490,7 @@ trait BaseAuditingToolsSuite val templateManager = new ComponentTemplateManager(destNodeClientProvider.adminClient, esVersionUsed) templateManager.putTemplateAndWaitForIndexing(templateName = s"$newDataStream-settings", body = template) - rorApiManager.updateRorInIndexConfig(rorConfigWithDataStreamAudit(newDataStream)).forceOkStatus() + rorApiManager.updateRorInIndexSettings(rorSettingsWithDataStreamAudit(newDataStream)).forceOkStatus() eventually { assertDataStreamExists(newDataStream) @@ -519,7 +519,7 @@ trait BaseAuditingToolsSuite ) ) - rorApiManager.updateRorInIndexConfig(rorConfigWithDataStreamAudit(newDataStream)).forceOkStatus() + rorApiManager.updateRorInIndexSettings(rorSettingsWithDataStreamAudit(newDataStream)).forceOkStatus() eventually { assertDataStreamExists(newDataStream) @@ -536,7 +536,7 @@ trait BaseAuditingToolsSuite createAuditDataStream(dataStreamName) - rorApiManager.updateRorInIndexConfig(rorConfigWithDataStreamAudit(dataStreamName)).forceOkStatus() + rorApiManager.updateRorInIndexSettings(rorSettingsWithDataStreamAudit(dataStreamName)).forceOkStatus() val adminAuditManager = new AuditIndexManager(destNodeClientProvider.adminClient, esVersionUsed, dataStreamName) auditEventAssertion(adminAuditManager) @@ -554,11 +554,11 @@ trait BaseAuditingToolsSuite } private def disableAudit(): Unit = { - val initialConfig = getResourceContent("/ror_audit/disabled_auditing_tools/readonlyrest.yml") - rorApiManager.updateRorInIndexConfig(initialConfig).forceOKStatusOrConfigAlreadyLoaded() + val initialSettings = getResourceContent("/ror_audit/disabled_auditing_tools/readonlyrest.yml") + rorApiManager.updateRorInIndexSettings(initialSettings).forceOKStatusOrSettingsAlreadyLoaded() } - private def auditEventAssertion(adminAuditManager: AuditIndexManager) = { + private def auditEventAssertion(adminAuditManager: AuditIndexManager): Unit = { val indexManager = new IndexManager(basicAuthClient("username", "dev"), esVersionUsed) val indexResponse = indexManager.getIndex("twitter") indexResponse should have statusCode 200 @@ -574,15 +574,15 @@ trait BaseAuditingToolsSuite } } - private def rorConfigWithIndexAudit(indexName: String) = { - baseRorConfig.replace( + private def rorSettingsWithIndexAudit(indexName: String) = { + baseRorSettingsYaml.replace( baseAuditIndexName, indexName ) } - private def rorConfigWithDataStreamAudit(dataStreamName: String) = { - baseRorConfig.replace( + private def rorSettingsWithDataStreamAudit(dataStreamName: String) = { + baseRorSettingsYaml.replace( baseAuditDataStreamName.getOrElse(throw new IllegalStateException("Data stream name should be set for Data Stream audit test")), dataStreamName ) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/XpackClusterWithRorNodesAndInternodeSslSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/XpackClusterWithRorNodesAndInternodeSslSuite.scala index f9f0ead53b..421df3d4d8 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/XpackClusterWithRorNodesAndInternodeSslSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/XpackClusterWithRorNodesAndInternodeSslSuite.scala @@ -53,7 +53,7 @@ trait XpackClusterWithRorNodesAndInternodeSslSuite nodeTypes = NonEmptyList.of( NodeType( securityType = RorSecurity(ReadonlyRestPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFileName, + rorSettingsFileName = rorSettingsFileName, restSsl = Enabled.Yes(RestSsl.Ror(SourceFile.RorFile)), internodeSsl = Enabled.Yes(InternodeSsl.Ror(SourceFile.RorFile)) )), @@ -77,11 +77,11 @@ trait XpackClusterWithRorNodesAndInternodeSslSuite response should have statusCode 200 } - "ROR config reload can be done" in { + "ROR settings reload can be done" in { val rorApiManager = new RorApiManager(clusterContainer.nodes.head.adminClient, esVersion = esVersionUsed) val updateResult = rorApiManager - .updateRorInIndexConfig(getResourceContent("/xpack_cluster_with_ror_nodes_and_internode_ssl/readonlyrest_update.yml")) + .updateRorInIndexSettings(getResourceContent("/xpack_cluster_with_ror_nodes_and_internode_ssl/readonlyrest_update.yml")) updateResult should have statusCode 200 updateResult.responseJson("status").str should be("ok") diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/support.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/support.scala index 1302096571..11d0478bdb 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/support.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/base/support.scala @@ -26,7 +26,7 @@ import tech.beshu.ror.utils.containers.{DependencyDef, EsClusterContainer, EsClu object support { trait BaseEsClusterIntegrationTest - extends RorConfigFileNameProvider + extends RorSettingsFileNameProvider with MultipleClientsSupport with TestSuiteWithClosedTaskAssertion with ForAllTestContainer { @@ -38,7 +38,7 @@ object support { } trait BaseEsRemoteClusterIntegrationTest - extends RorConfigFileNameProvider + extends RorSettingsFileNameProvider with MultipleClientsSupport with TestSuiteWithClosedTaskAssertion with ForAllTestContainer { @@ -50,7 +50,7 @@ object support { } trait BaseManyEsClustersIntegrationTest - extends RorConfigFileNameProvider + extends RorSettingsFileNameProvider with MultipleClientsSupport with TestSuiteWithClosedTaskAssertion with ForAllTestContainer { @@ -65,7 +65,7 @@ object support { } trait BaseSingleNodeEsClusterTest - extends RorConfigFileNameProvider + extends RorSettingsFileNameProvider with SingleClientSupport with TestSuiteWithClosedTaskAssertion with NodeInitializerProvider { diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleEsEngineSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleEsEngineSuite.scala index a28672ec08..42aeabe519 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleEsEngineSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleEsEngineSuite.scala @@ -23,7 +23,7 @@ class FieldRuleEsEngineSuite extends FieldRuleEngineSuite with SingletonPluginTestSupport { - override implicit val rorConfigFileName: String = "/field_level_security_engine/readonlyrest_fls_engine_es.yml" + override implicit val rorSettingsFileName: String = "/field_level_security_engine/readonlyrest_fls_engine_es.yml" override protected def unmodifiableQueryAssertion(result: SearchManager#SearchResult): Unit = { result should have statusCode 403 diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleEsWithLuceneEngineSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleEsWithLuceneEngineSuite.scala index 62df74b3c6..77d861be7a 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleEsWithLuceneEngineSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleEsWithLuceneEngineSuite.scala @@ -23,7 +23,7 @@ class FieldRuleEsWithLuceneEngineSuite extends FieldRuleEngineSuite with SingletonPluginTestSupport { - override implicit val rorConfigFileName: String = "/field_level_security_engine/readonlyrest_fls_engine_es_with_lucene.yml" + override implicit val rorSettingsFileName: String = "/field_level_security_engine/readonlyrest_fls_engine_es_with_lucene.yml" override protected def unmodifiableQueryAssertion(result: SearchManager#SearchResult): Unit = { result should have statusCode 200 diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleLuceneEngineSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleLuceneEngineSuite.scala index 641159781d..c91acaec00 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleLuceneEngineSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/engine/FieldRuleLuceneEngineSuite.scala @@ -23,7 +23,7 @@ class FieldRuleLuceneEngineSuite extends FieldRuleEngineSuite with SingletonPluginTestSupport { - override implicit val rorConfigFileName: String = "/field_level_security_engine/readonlyrest_fls_engine_lucene.yml" + override implicit val rorSettingsFileName: String = "/field_level_security_engine/readonlyrest_fls_engine_lucene.yml" override protected def unmodifiableQueryAssertion(result: SearchManager#SearchResult): Unit = { result should have statusCode 200 diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/querydsl/FieldRuleQueryDSLSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/querydsl/FieldRuleQueryDSLSuite.scala index 497dae3b95..a245f7831a 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/querydsl/FieldRuleQueryDSLSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/querydsl/FieldRuleQueryDSLSuite.scala @@ -30,7 +30,7 @@ trait FieldRuleQueryDSLSuite with ESVersionSupportForAnyWordSpecLike { this: EsClusterProvider => - override implicit val rorConfigFileName: String = "/field_level_security_query/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/field_level_security_query/readonlyrest.yml" override def nodeDataInitializer = Some(FieldRuleQueryDSLSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/sourcefiltering/FieldRuleSourceFilteringSuite.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/sourcefiltering/FieldRuleSourceFilteringSuite.scala index 0c1128116e..47896d3250 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/sourcefiltering/FieldRuleSourceFilteringSuite.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/suites/fields/sourcefiltering/FieldRuleSourceFilteringSuite.scala @@ -37,7 +37,7 @@ trait FieldRuleSourceFilteringSuite protected type CALL_RESULT <: BaseManager#JsonResponse - override implicit val rorConfigFileName: String = "/field_level_security/readonlyrest.yml" + override implicit val rorSettingsFileName: String = "/field_level_security/readonlyrest.yml" override def nodeDataInitializer: Option[ElasticsearchNodeDataInitializer] = Some(FieldRuleSourceFilteringSuite.nodeDataInitializer()) diff --git a/integration-tests/src/test/scala/tech/beshu/ror/integration/utils/PluginTestSupport.scala b/integration-tests/src/test/scala/tech/beshu/ror/integration/utils/PluginTestSupport.scala index 3103ca1b17..add4ea989e 100644 --- a/integration-tests/src/test/scala/tech/beshu/ror/integration/utils/PluginTestSupport.scala +++ b/integration-tests/src/test/scala/tech/beshu/ror/integration/utils/PluginTestSupport.scala @@ -31,21 +31,21 @@ trait SingletonPluginTestSupport extends PluginTestSupport with EsClusterProvider with BeforeAndAfterAll - with ResolvedRorConfigFileProvider { + with ResolvedRorSettingsFileProvider { this: Suite with BaseSingleNodeEsClusterTest => override lazy val targetEs: EsContainer = SingletonEsContainerWithRorSecurity.singleton.nodes.head private var startedDependencies = StartedClusterDependencies(Nil) - override final def resolvedRorConfigFile: File = { - resolveConfig.toTry.get + override final def resolvedRorSettingsFile: File = { + resolveSettings.toTry.get } override protected def beforeAll(): Unit = { startedDependencies = DependencyRunner.startDependencies(clusterDependencies) SingletonEsContainerWithRorSecurity.cleanUpContainer() - SingletonEsContainerWithRorSecurity.updateConfig(resolvedRorConfigFile.contentAsString) + SingletonEsContainerWithRorSecurity.updateSettings(resolvedRorSettingsFile.contentAsString) nodeDataInitializer.foreach(SingletonEsContainerWithRorSecurity.initNode) super.beforeAll() } @@ -55,16 +55,16 @@ trait SingletonPluginTestSupport startedDependencies.values.foreach(started => started.container.stop()) } - private def resolveConfig: Either[Throwable, File] = { + private def resolveSettings: Either[Throwable, File] = { Either.cond( test = startedDependencies.values.size === clusterDependencies.size, - right = resolvedConfig(startedDependencies), - left = new IllegalStateException("Not all dependencies are started. Cannot read resolved config yet") + right = resolvedSettings(startedDependencies), + left = new IllegalStateException("Not all dependencies are started. Cannot read resolved settings yet") ) } - private def resolvedConfig(startedDependencies: StartedClusterDependencies) = { - val configFile = File.apply(getResourcePath(rorConfigFileName)) - RorConfigAdjuster.adjustUsingDependencies(configFile, startedDependencies) + private def resolvedSettings(startedDependencies: StartedClusterDependencies) = { + val settingsFile = File.apply(getResourcePath(rorSettingsFileName)) + RorSettingsAdjuster.adjustUsingDependencies(settingsFile, startedDependencies) } } \ No newline at end of file diff --git a/ror-shadowed-libs/build.gradle b/ror-shadowed-libs/build.gradle index a6f795cced..4177e35083 100644 --- a/ror-shadowed-libs/build.gradle +++ b/ror-shadowed-libs/build.gradle @@ -18,7 +18,7 @@ plugins { id "readonlyrest.base-common-conventions" - id "com.gradleup.shadow" version "9.0.1" + id "com.gradleup.shadow" version "9.1.0" id "java" id "maven-publish" } diff --git a/ror-tools-core/build.gradle b/ror-tools-core/build.gradle index 6919910bbd..7c4326bb11 100644 --- a/ror-tools-core/build.gradle +++ b/ror-tools-core/build.gradle @@ -17,7 +17,7 @@ plugins { id "readonlyrest.base-common-conventions" - id "com.github.johnrengelman.shadow" version "8.1.1" + id "com.gradleup.shadow" version "9.1.0" id "com.github.maiflai.scalatest" version "0.33" id "java-library" } @@ -81,8 +81,8 @@ shadowJar { import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar tasks.named('shadowJar', ShadowJar) { - enableRelocation true - relocationPrefix "tech.beshu.ror.tools" + enableAutoRelocation = true + relocationPrefix = "tech.beshu.ror.tools" } tasks.withType(AbstractArchiveTask).configureEach { diff --git a/ror-tools-core/src/main/scala/tech/beshu/ror/tools/core/patches/internal/modifiers/BytecodeJarModifier.scala b/ror-tools-core/src/main/scala/tech/beshu/ror/tools/core/patches/internal/modifiers/BytecodeJarModifier.scala index bdc8800e72..36e0c94425 100644 --- a/ror-tools-core/src/main/scala/tech/beshu/ror/tools/core/patches/internal/modifiers/BytecodeJarModifier.scala +++ b/ror-tools-core/src/main/scala/tech/beshu/ror/tools/core/patches/internal/modifiers/BytecodeJarModifier.scala @@ -16,8 +16,8 @@ */ package tech.beshu.ror.tools.core.patches.internal.modifiers +import better.files.FileExtensions import tech.beshu.ror.tools.core.utils.FileUtils.* -import tech.beshu.ror.tools.core.utils.FileUtils.javaFileToFile import java.io.{ByteArrayInputStream, File, InputStream} import java.net.URI @@ -32,7 +32,7 @@ private[patches] abstract class BytecodeJarModifier(debugEnabled: Boolean = fals protected def modifyFileInJar(jar: File, filePathString: String, processFileContent: InputStream => Array[Byte]): Unit = { - val originalFilePermissionsAndOwner = jar.getFilePermissionsAndOwner + val originalFilePermissionsAndOwner = jar.toScala.getFilePermissionsAndOwner val modifiedFileContent = loadAndProcessFileFromJar( jar = jar, filePathString = filePathString, @@ -43,7 +43,7 @@ private[patches] abstract class BytecodeJarModifier(debugEnabled: Boolean = fals destinationPathSting = filePathString, newContent = modifiedFileContent ) - jar.setFilePermissionsAndOwner(originalFilePermissionsAndOwner) + jar.toScala.setFilePermissionsAndOwner(originalFilePermissionsAndOwner) } private def loadAndProcessFileFromJar(jar: File, diff --git a/ror-tools/build.gradle b/ror-tools/build.gradle index 701dbbef36..a8176d5907 100644 --- a/ror-tools/build.gradle +++ b/ror-tools/build.gradle @@ -23,8 +23,8 @@ buildscript { plugins { id "readonlyrest.base-common-conventions" - id "com.github.johnrengelman.shadow" version "8.1.1" id "com.github.maiflai.scalatest" version "0.33" + id "com.gradleup.shadow" version "9.1.0" } repositories { @@ -43,6 +43,9 @@ sourceSets { } test { + // Run tests with Java 21 (required by Gradle Tooling API used in tests, matches CI environment) + javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(17) } + systemProperty "project.dir", rootProject.projectDir enabled = project.hasProperty('esModule') if (enabled) { @@ -110,8 +113,8 @@ shadowJar { import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar tasks.named('shadowJar', ShadowJar) { - enableRelocation true - relocationPrefix "tech.beshu.ror.tools" + enableAutoRelocation = true + relocationPrefix = "tech.beshu.ror.tools" } tasks.withType(AbstractArchiveTask).configureEach { diff --git a/ror-tools/src/test/scala/tech/beshu/ror/tools/PatchingOfAptBasedEsInstallationSuite.scala b/ror-tools/src/test/scala/tech/beshu/ror/tools/PatchingOfAptBasedEsInstallationSuite.scala index 6edaa0682d..0d4075ad72 100644 --- a/ror-tools/src/test/scala/tech/beshu/ror/tools/PatchingOfAptBasedEsInstallationSuite.scala +++ b/ror-tools/src/test/scala/tech/beshu/ror/tools/PatchingOfAptBasedEsInstallationSuite.scala @@ -69,7 +69,7 @@ class PatchingOfAptBasedEsInstallationSuite extends AnyWordSpec with ESVersionSu } dockerLogs should include("ReadonlyREST is waiting for full Elasticsearch init") dockerLogs should include("Elasticsearch fully initiated. ReadonlyREST can continue ...") - dockerLogs should include("Loading Elasticsearch settings from file:") + dockerLogs should include("Loading ReadonlyREST main settings from file") dockerLogs should include("Cannot verify if the ES was patched") dockerLogs should include("ReadonlyREST was loaded") } @@ -84,7 +84,7 @@ class PatchingOfAptBasedEsInstallationSuite extends AnyWordSpec with ESVersionSu } dockerLogs should include("ReadonlyREST is waiting for full Elasticsearch init") dockerLogs should include("Elasticsearch fully initiated. ReadonlyREST can continue ...") - dockerLogs should include("Loading Elasticsearch settings from file:") + dockerLogs should include("Loading ReadonlyREST main settings from file") dockerLogs shouldNot include("Cannot verify if the ES was patched") dockerLogs should include("ReadonlyREST was loaded") } @@ -99,7 +99,7 @@ class PatchingOfAptBasedEsInstallationSuite extends AnyWordSpec with ESVersionSu } dockerLogs should include("ReadonlyREST is waiting for full Elasticsearch init") dockerLogs should include("Elasticsearch fully initiated. ReadonlyREST can continue ...") - dockerLogs should include("Loading Elasticsearch settings from file:") + dockerLogs should include("Loading ReadonlyREST main settings from file") dockerLogs shouldNot include("Cannot verify if the ES was patched") dockerLogs should include("ReadonlyREST was loaded") } @@ -112,7 +112,7 @@ class PatchingOfAptBasedEsInstallationSuite extends AnyWordSpec with ESVersionSu } dockerLogs should include("ReadonlyREST is waiting for full Elasticsearch init") dockerLogs should include("Elasticsearch fully initiated. ReadonlyREST can continue ...") - dockerLogs should include("Loading Elasticsearch settings from file:") + dockerLogs should include("Loading ReadonlyREST main settings from file") dockerLogs shouldNot include("Cannot verify if the ES was patched") dockerLogs should include("ReadonlyREST was loaded") } @@ -122,7 +122,7 @@ class PatchingOfAptBasedEsInstallationSuite extends AnyWordSpec with ESVersionSu } dockerLogs should include("ReadonlyREST is waiting for full Elasticsearch init") dockerLogs should include("Elasticsearch fully initiated. ReadonlyREST can continue ...") - dockerLogs should include("Loading Elasticsearch settings from file:") + dockerLogs should include("Loading ReadonlyREST main settings from file") dockerLogs should include("Cannot verify if the ES was patched. component [readonlyrest], module [ALL-UNNAMED], class [class tech.beshu.ror.tools.core.utils.EsDirectory$], entitlement [file], operation [read], path [/usr/share/elasticsearch]") dockerLogs should include("ReadonlyREST was loaded") } @@ -211,7 +211,7 @@ private object PatchingOfAptBasedEsInstallationSuite extends EsModulePatterns { clusterName = clusterName, securityType = SecurityType.RorWithXpackSecurity( ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigFileName = rorConfigFile + rorSettingsFileName = rorConfigFile ) ), containerSpecification = ContainerSpecification.empty, diff --git a/ror-tools/src/test/scala/tech/beshu/ror/tools/utils/ExampleEsWithRorContainer.scala b/ror-tools/src/test/scala/tech/beshu/ror/tools/utils/ExampleEsWithRorContainer.scala index b9f1284387..f47e9e671d 100644 --- a/ror-tools/src/test/scala/tech/beshu/ror/tools/utils/ExampleEsWithRorContainer.scala +++ b/ror-tools/src/test/scala/tech/beshu/ror/tools/utils/ExampleEsWithRorContainer.scala @@ -26,7 +26,6 @@ import tech.beshu.ror.utils.containers.* import tech.beshu.ror.utils.containers.ElasticsearchNodeWaitingStrategy.AwaitingReadyStrategy import tech.beshu.ror.utils.containers.EsContainerCreator.EsNodeSettings import tech.beshu.ror.utils.containers.exceptions.ContainerCreationException -import tech.beshu.ror.utils.containers.images.Elasticsearch.EsInstallationType import tech.beshu.ror.utils.containers.images.domain.Enabled import tech.beshu.ror.utils.containers.images.{Elasticsearch, ReadonlyRestWithEnabledXpackSecurityPlugin} import tech.beshu.ror.utils.containers.windows.{WindowsEsDirectoryManager, WindowsEsSetup} @@ -66,8 +65,8 @@ class ExampleEsWithRorContainer(implicit scheduler: Scheduler) extends EsContain val clusterName = s"ROR_${uniqueClusterId.getAndIncrement()}" val nodeName = s"${clusterName}_1" val attributes = ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes.default.copy( - rorConfigReloading = Enabled.No, - rorConfigFileName = "/basic/readonlyrest.yml", + rorSettingsReloading = Enabled.No, + rorSettingsFileName = "/basic/readonlyrest.yml", ) createCustomES( nodeSettings = EsNodeSettings( @@ -91,9 +90,9 @@ class ExampleEsWithRorContainer(implicit scheduler: Scheduler) extends EsContain startedClusterDependencies: StartedClusterDependencies) = { val project = RorPluginGradleProject.fromSystemProperty val pluginFile: File = project.assemble.getOrElse(throw new ContainerCreationException("Plugin not assembled, build the plugin or run the test from Gradle")) - val rawRorConfigFile = ContainerUtils.getResourceFile(attributes.rorConfigFileName) + val rawRorConfigFile = ContainerUtils.getResourceFile(attributes.rorSettingsFileName) - val adjustedRorConfig = RorConfigAdjuster.adjustUsingDependencies( + val adjustedRorConfig = RorSettingsAdjuster.adjustUsingDependencies( source = rawRorConfigFile.toScala, startedDependencies = startedClusterDependencies, ) @@ -110,7 +109,7 @@ class ExampleEsWithRorContainer(implicit scheduler: Scheduler) extends EsContain ), securityConfig = ReadonlyRestWithEnabledXpackSecurityPlugin.Config( rorPlugin = pluginFile.toScala, - rorConfig = adjustedRorConfig, + rorSettings = adjustedRorConfig, attributes = attributes, ), initializer = nodeDataInitializer, diff --git a/tests-utils/build.gradle b/tests-utils/build.gradle index 3622843868..ac4884a540 100644 --- a/tests-utils/build.gradle +++ b/tests-utils/build.gradle @@ -74,8 +74,8 @@ dependencies { api group: 'org.scala-lang.modules' , name: 'scala-parallel-collections_3', version: '1.0.4' api group: 'com.typesafe.scala-logging', name: 'scala-logging_3', version: '3.9.5' api group: 'org.scalatest', name: 'scalatest_3', version: '3.2.19' - api group: 'com.dimafeng', name: 'testcontainers-scala-scalatest_3', version: '0.43.0' - api group: 'org.testcontainers', name: 'testcontainers', version: "1.20.6" + api group: 'com.dimafeng', name: 'testcontainers-scala-scalatest_3', version: '0.44.0' + api group: 'org.testcontainers', name: 'testcontainers', version: "2.0.2" api group: 'eu.rekawek.toxiproxy', name: 'toxiproxy-java', version: "2.1.7" api group: 'com.unboundid', name: 'unboundid-ldapsdk', version: '6.0.11' api group: 'com.lihaoyi', name: 'upickle_3', version: '4.2.1' diff --git a/tests-utils/src/main/resources/log4j2_es_7.10_and_newer.properties b/tests-utils/src/main/resources/log4j2_es_7.10_and_newer.properties index 2fa3a9662c..b219fe63f9 100644 --- a/tests-utils/src/main/resources/log4j2_es_7.10_and_newer.properties +++ b/tests-utils/src/main/resources/log4j2_es_7.10_and_newer.properties @@ -84,5 +84,5 @@ logger.index_indexing_slowlog.additivity=false appender.header_warning.type = HeaderWarningAppender appender.header_warning.name = header_warning -logger.ror_ldap.name=tech.beshu.ror.accesscontrol.blocks.definitions.ldap.implementations -logger.ror_ldap.level=debug \ No newline at end of file +logger.ror_ldap.name=tech.beshu.ror +logger.ror_ldap.level=info \ No newline at end of file diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/DnsServerContainer.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/DnsServerContainer.scala index 504cae4fb5..b3d76924fd 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/DnsServerContainer.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/DnsServerContainer.scala @@ -24,7 +24,7 @@ import scala.annotation.nowarn import scala.jdk.CollectionConverters.* @nowarn("cat=deprecation") -class DnsServerContainer(srvServicePort: Int) +class DnsServerContainer(srvServiceHost: String, srvServicePort: Int) extends GenericContainer( dockerImage = new ImageFromDockerfile() .withFileFromClasspath("Dockerfile", "coredns-image/Dockerfile") @@ -33,7 +33,7 @@ class DnsServerContainer(srvServicePort: Int) s""" |$$ORIGIN example.org. |@ 3600 IN SOA someorg.org. someorg.com. (2017042745 7200 3600 1209600 3600) - |_ldap._tcp. 86400 IN SRV 10 60 $srvServicePort localhost. + |_ldap._tcp. 86400 IN SRV 10 60 $srvServicePort $srvServiceHost. |""".stripMargin), ) { @@ -42,14 +42,18 @@ class DnsServerContainer(srvServicePort: Int) new ExposedPort(53, InternetProtocol.UDP) :: cmd.getExposedPorts.toList: _* ) - val ports = cmd.getPortBindings - ports.bind(ExposedPort.udp(53), Ports.Binding.empty()) + val ports = new Ports() + ports.bind(ExposedPort.udp(53), Ports.Binding.bindPort(0)) cmd.withPortBindings(ports) } + def dnsHost: String = this.containerIpAddress + def dnsPort: Int = { - // This is hack to obtain mapping of UDP port as testcontainers doesn't allow explicit mapping of UDP ports + // This is hack to obtain mapping of UDP port as testcontainers doesn't allow explicit mapping of UDP ports, // although it is mapping each port exposed by container(even UDP). + // Wait a bit to ensure port binding is ready + Thread.sleep(500) this.containerInfo .getNetworkSettings .getPorts diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/ElasticsearchNodeWaitingStrategy.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/ElasticsearchNodeWaitingStrategy.scala index ebb2da04f5..d3b2f51996 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/ElasticsearchNodeWaitingStrategy.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/ElasticsearchNodeWaitingStrategy.scala @@ -19,51 +19,86 @@ package tech.beshu.ror.utils.containers import com.typesafe.scalalogging.StrictLogging import monix.eval.Coeval import org.testcontainers.containers.ContainerLaunchException -import org.testcontainers.containers.wait.strategy.AbstractWaitStrategy +import org.testcontainers.containers.wait.strategy.{AbstractWaitStrategy, HttpWaitStrategy} import tech.beshu.ror.utils.containers.ElasticsearchNodeWaitingStrategy.AwaitingReadyStrategy import tech.beshu.ror.utils.httpclient.RestClient -import tech.beshu.ror.utils.misc.{EsStartupChecker, Version} +import tech.beshu.ror.utils.misc.OsUtils.CurrentOs +import tech.beshu.ror.utils.misc.{EsStartupChecker, OsUtils, Version} +import scala.language.postfixOps import scala.util.Try class ElasticsearchNodeWaitingStrategy(esVersion: String, + esPort: Int, containerName: String, restClient: Coeval[RestClient], - initializer: ElasticsearchNodeDataInitializer = NoOpElasticsearchNodeDataInitializer, + initializer: ElasticsearchNodeDataInitializer, strategy: AwaitingReadyStrategy) extends AbstractWaitStrategy with StrictLogging { override def waitUntilReady(): Unit = { - val client = restClient.runAttempt().fold(throw _, identity) - val checker = - if (Version.greaterOrEqualThan(esVersion, 8, 3, 0)) { - EsStartupChecker.greenEsClusterChecker(containerName, client) - } else { - EsStartupChecker.accessibleEsChecker(containerName, client) - } - val started = strategy match { + val client = createRestClient() + if (!waitForStart(client)) { + throw new ContainerLaunchException(s"Cannot start ROR-ES container [$containerName]") + } + initialize(client) + } + + private def waitForStart(client: RestClient) = { + strategy match { case AwaitingReadyStrategy.WaitForEsReadiness => - checker.waitForStart() + createWaitForReadinessChecker(client).waitForStart() case AwaitingReadyStrategy.ImmediatelyTreatAsReady => true + case AwaitingReadyStrategy.WaitForEsRestApiResponsive => + waitForRestEsApi(client) } - if (!started) { - throw new ContainerLaunchException(s"Cannot start ROR-ES container [$containerName]") - } + } + + private def initialize(client: RestClient): Unit = { Try(initializer.initialize(esVersion, client)) .fold( ex => throw new ContainerLaunchException(s"Cannot start ROR-ES container [$containerName]", ex), identity ) } + + private def createRestClient() = { + restClient.runAttempt().fold(throw _, identity) + } + + private def createWaitForReadinessChecker(client: RestClient) = { + if (Version.greaterOrEqualThan(esVersion, 8, 3, 0)) { + EsStartupChecker.greenEsClusterChecker(containerName, client) + } else { + EsStartupChecker.accessibleEsChecker(containerName, client) + } + } + + private def waitForRestEsApi(client: RestClient) = { + OsUtils.currentOs match { + case CurrentOs.Windows => + EsStartupChecker + .reachableEsChecker(containerName, client) + .waitForStart() + case CurrentOs.OtherThanWindows => + val esRestApiWaitStrategy = new HttpWaitStrategy() + .usingTls().allowInsecure() + .forPort(esPort) + .forPath("/") + .forStatusCodeMatching(_ => true) + Try(esRestApiWaitStrategy.waitUntilReady(waitStrategyTarget)).isSuccess + } + } } object ElasticsearchNodeWaitingStrategy { sealed trait AwaitingReadyStrategy object AwaitingReadyStrategy { - case object WaitForEsReadiness extends AwaitingReadyStrategy - case object ImmediatelyTreatAsReady extends AwaitingReadyStrategy + case object WaitForEsReadiness extends AwaitingReadyStrategy + case object ImmediatelyTreatAsReady extends AwaitingReadyStrategy + case object WaitForEsRestApiResponsive extends AwaitingReadyStrategy } } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsClusterContainer.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsClusterContainer.scala index 5b538e55a0..2c689c5913 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsClusterContainer.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsClusterContainer.scala @@ -70,8 +70,8 @@ class EsClusterContainer private[containers](val esClusterSettings: EsClusterSet .runSyncUnsafe() } - def resolvedRorConfig(config: String): String = { - RorConfigAdjuster.adjustUsingDependencies(config, aStartedDependencies) + def resolvedRorSettings(config: String): String = { + RorSettingsAdjuster.adjustUsingDependencies(config, aStartedDependencies) } } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainer.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainer.scala index c534911217..61ddc81718 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainer.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainer.scala @@ -29,7 +29,7 @@ import tech.beshu.ror.utils.containers.EsContainer.{Credentials, EsContainerImpl import tech.beshu.ror.utils.containers.images.{DockerImageCreator, Elasticsearch} import tech.beshu.ror.utils.containers.logs.CompositeLogConsumer import tech.beshu.ror.utils.containers.providers.ClientProvider -import tech.beshu.ror.utils.containers.windows.WindowsPseudoEsContainer +import tech.beshu.ror.utils.containers.windows.{WindowsEsPortProvider, WindowsPseudoEsContainer} import tech.beshu.ror.utils.httpclient.RestClient import tech.beshu.ror.utils.misc.OsUtils import tech.beshu.ror.utils.misc.OsUtils.CurrentOs @@ -53,11 +53,17 @@ abstract class EsContainer(val esVersion: String, private val esClient = Coeval(adminClient) - private val waitStrategy = new ElasticsearchNodeWaitingStrategy(esVersion, esConfig.nodeName, esClient, initializer, awaitingReadyStrategy) - private val containerImplementation: EsContainerImplementation = { OsUtils.currentOs match { case CurrentOs.Windows => + val waitStrategy = new ElasticsearchNodeWaitingStrategy( + esVersion = esVersion, + esPort = WindowsEsPortProvider.get(esConfig.nodeName).esPort, + containerName = esConfig.nodeName, + restClient = esClient, + initializer = initializer, + strategy = awaitingReadyStrategy + ) EsContainerImplementation.Windows( container = new WindowsPseudoEsContainer(elasticsearch, waitStrategy, additionalLogConsumer), ) @@ -69,6 +75,14 @@ abstract class EsContainer(val esVersion: String, case Some(additional) => new CompositeLogConsumer(slf4jConsumer, additional) case scala.None => slf4jConsumer } + val waitStrategy = new ElasticsearchNodeWaitingStrategy( + esVersion = esVersion, + esPort = 9200, + containerName = esConfig.nodeName, + restClient = esClient, + initializer = initializer, + strategy = awaitingReadyStrategy + ) container.setLogConsumers((logConsumer :: Nil).asJava) container.addExposedPort(9200) container.addExposedPort(9300) @@ -123,6 +137,15 @@ abstract class EsContainer(val esVersion: String, case None => new RestClient(sslEnabled, ip, port, Option.empty) } + override def start(): Unit = { + try { + super.start() + } catch { + case ex: Throwable => + logger.error("Container starting error", ex) + throw ex + } + } } object EsContainer { diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerCreator.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerCreator.scala index 36dc08cbc4..34a69bca3d 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerCreator.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerCreator.scala @@ -21,6 +21,7 @@ import cats.data.NonEmptyList import com.dimafeng.testcontainers.SingleContainer import org.testcontainers.containers.GenericContainer as JavaGenericContainer import org.testcontainers.containers.output.OutputFrame +import tech.beshu.ror.utils.containers.ElasticsearchNodeWaitingStrategy.AwaitingReadyStrategy import tech.beshu.ror.utils.containers.EsContainerCreator.EsNodeSettings import tech.beshu.ror.utils.containers.exceptions.ContainerCreationException import tech.beshu.ror.utils.containers.images.Elasticsearch.EsInstallationType @@ -53,20 +54,21 @@ trait EsContainerCreator { nodeDataInitializer: ElasticsearchNodeDataInitializer, startedClusterDependencies: StartedClusterDependencies, esInstallationType: EsInstallationType = defaultEsInstallationType, - additionalLogConsumer: Option[Consumer[OutputFrame]] = None): EsContainer = { + additionalLogConsumer: Option[Consumer[OutputFrame]] = None, + awaitingReadyStrategy: AwaitingReadyStrategy = AwaitingReadyStrategy.WaitForEsReadiness): EsContainer = { val project = nodeSettings.esVersion match { case EsVersion.DeclaredInProject => RorPluginGradleProject.fromSystemProperty case EsVersion.SpecificVersion(version) => RorPluginGradleProject.customModule(version) } nodeSettings.securityType match { case SecurityType.RorWithXpackSecurity(attributes) => - createEsWithRorAndXpackSecurityContainer(nodeSettings, allNodeNames, project, nodeDataInitializer, attributes, startedClusterDependencies, esInstallationType, additionalLogConsumer) + createEsWithRorAndXpackSecurityContainer(nodeSettings, allNodeNames, project, nodeDataInitializer, attributes, startedClusterDependencies, esInstallationType, additionalLogConsumer, awaitingReadyStrategy) case SecurityType.RorSecurity(attributes) => - createEsWithRorContainer(nodeSettings, allNodeNames, project, nodeDataInitializer, attributes, startedClusterDependencies, esInstallationType, additionalLogConsumer) + createEsWithRorContainer(nodeSettings, allNodeNames, project, nodeDataInitializer, attributes, startedClusterDependencies, esInstallationType, additionalLogConsumer, awaitingReadyStrategy) case SecurityType.XPackSecurity(attributes) => - createEsWithXpackContainer(nodeSettings, allNodeNames, project, nodeDataInitializer, attributes, startedClusterDependencies, esInstallationType, additionalLogConsumer) + createEsWithXpackContainer(nodeSettings, allNodeNames, project, nodeDataInitializer, attributes, startedClusterDependencies, esInstallationType, additionalLogConsumer, awaitingReadyStrategy) case SecurityType.NoSecurityCluster => - createEsWithNoSecurityContainer(nodeSettings, allNodeNames, project, nodeDataInitializer, startedClusterDependencies, esInstallationType, additionalLogConsumer) + createEsWithNoSecurityContainer(nodeSettings, allNodeNames, project, nodeDataInitializer, startedClusterDependencies, esInstallationType, additionalLogConsumer, awaitingReadyStrategy) } } @@ -77,12 +79,13 @@ trait EsContainerCreator { attributes: ReadonlyRestWithEnabledXpackSecurityPlugin.Config.Attributes, startedClusterDependencies: StartedClusterDependencies, esInstallationType: EsInstallationType, - additionalLogConsumer: Option[Consumer[OutputFrame]]) = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy) = { val rorPluginFile: File = project.assemble.getOrElse(throw new ContainerCreationException("Plugin file assembly failed")) - val rawRorConfigFile = ContainerUtils.getResourceFile(attributes.rorConfigFileName) + val rawRorSettingsFile = ContainerUtils.getResourceFile(attributes.rorSettingsFileName) - val adjustedRorConfig = RorConfigAdjuster.adjustUsingDependencies( - source = rawRorConfigFile.toScala, + val adjustedRorSettings = RorSettingsAdjuster.adjustUsingDependencies( + source = rawRorSettingsFile.toScala, startedDependencies = startedClusterDependencies, ) @@ -98,12 +101,13 @@ trait EsContainerCreator { ), securityConfig = ReadonlyRestWithEnabledXpackSecurityPlugin.Config( rorPlugin = rorPluginFile.toScala, - rorConfig = adjustedRorConfig, + rorSettings = adjustedRorSettings, attributes = attributes ), initializer = nodeDataInitializer, startedClusterDependencies = startedClusterDependencies, additionalLogConsumer = additionalLogConsumer, + awaitingReadyStrategy = awaitingReadyStrategy ) } @@ -114,12 +118,13 @@ trait EsContainerCreator { attributes: ReadonlyRestPlugin.Config.Attributes, startedClusterDependencies: StartedClusterDependencies, esInstallationType: EsInstallationType, - additionalLogConsumer: Option[Consumer[OutputFrame]]) = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy) = { val rorPluginFile: File = project.assemble.getOrElse(throw new ContainerCreationException("Plugin file assembly failed")) - val rawRorConfigFile = ContainerUtils.getResourceFile(attributes.rorConfigFileName) + val rawRorSettingsFile = ContainerUtils.getResourceFile(attributes.rorSettingsFileName) - val adjustedRorConfig = RorConfigAdjuster.adjustUsingDependencies( - source = rawRorConfigFile.toScala, + val adjustedRorSettings = RorSettingsAdjuster.adjustUsingDependencies( + source = rawRorSettingsFile.toScala, startedDependencies = startedClusterDependencies, ) @@ -135,12 +140,13 @@ trait EsContainerCreator { ), rorConfig = ReadonlyRestPlugin.Config( rorPlugin = rorPluginFile.toScala, - rorConfig = adjustedRorConfig, + rorSettings = adjustedRorSettings, attributes = attributes ), initializer = nodeDataInitializer, startedClusterDependencies = startedClusterDependencies, additionalLogConsumer = additionalLogConsumer, + awaitingReadyStrategy = awaitingReadyStrategy ) } @@ -151,7 +157,8 @@ trait EsContainerCreator { attributes: XpackSecurityPlugin.Config.Attributes, startedClusterDependencies: StartedClusterDependencies, esInstallationType: EsInstallationType, - additionalLogConsumer: Option[Consumer[OutputFrame]]) = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy) = { EsContainerWithXpackSecurity.create( esVersion = project.getModuleESVersion, esConfig = Elasticsearch.Config( @@ -166,6 +173,7 @@ trait EsContainerCreator { initializer = nodeDataInitializer, startedClusterDependencies = startedClusterDependencies, additionalLogConsumer = additionalLogConsumer, + awaitingReadyStrategy = awaitingReadyStrategy ) } @@ -175,7 +183,8 @@ trait EsContainerCreator { nodeDataInitializer: ElasticsearchNodeDataInitializer, startedClusterDependencies: StartedClusterDependencies, esInstallationType: EsInstallationType, - additionalLogConsumer: Option[Consumer[OutputFrame]]) = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy) = { EsContainerWithNoSecurity.create( esVersion = project.getModuleESVersion, esConfig = Elasticsearch.Config( @@ -189,6 +198,7 @@ trait EsContainerCreator { initializer = nodeDataInitializer, startedClusterDependencies = startedClusterDependencies, additionalLogConsumer = additionalLogConsumer, + awaitingReadyStrategy = awaitingReadyStrategy ) } } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithNoSecurity.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithNoSecurity.scala index d6a3f19ea4..f5be296023 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithNoSecurity.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithNoSecurity.scala @@ -46,7 +46,8 @@ object EsContainerWithNoSecurity extends StrictLogging { esConfig: Elasticsearch.Config, initializer: ElasticsearchNodeDataInitializer, startedClusterDependencies: StartedClusterDependencies, - additionalLogConsumer: Option[Consumer[OutputFrame]]): EsContainer = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy): EsContainer = { new EsContainerWithNoSecurity( esConfig, esVersion, @@ -54,6 +55,7 @@ object EsContainerWithNoSecurity extends StrictLogging { esImageFromDockerfile(esVersion, esConfig), initializer, additionalLogConsumer, + awaitingReadyStrategy ) } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithRorAndXpackSecurity.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithRorAndXpackSecurity.scala index 62dc2ec442..e5e6471f65 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithRorAndXpackSecurity.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithRorAndXpackSecurity.scala @@ -51,7 +51,8 @@ object EsContainerWithRorAndXpackSecurity extends StrictLogging { securityConfig: ReadonlyRestWithEnabledXpackSecurityPlugin.Config, initializer: ElasticsearchNodeDataInitializer, startedClusterDependencies: StartedClusterDependencies, - additionalLogConsumer: Option[Consumer[OutputFrame]]): EsContainer = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy): EsContainer = { create( esVersion = esVersion, esConfig = esConfig, @@ -60,8 +61,8 @@ object EsContainerWithRorAndXpackSecurity extends StrictLogging { startedClusterDependencies = startedClusterDependencies, customEntrypoint = None, performPatching = true, - awaitingReadyStrategy = AwaitingReadyStrategy.WaitForEsReadiness, additionalLogConsumer = additionalLogConsumer, + awaitingReadyStrategy = awaitingReadyStrategy, ) } @@ -71,8 +72,8 @@ object EsContainerWithRorAndXpackSecurity extends StrictLogging { initializer: ElasticsearchNodeDataInitializer, startedClusterDependencies: StartedClusterDependencies, customEntrypoint: Option[Path], - awaitingReadyStrategy: AwaitingReadyStrategy, - additionalLogConsumer: Option[Consumer[OutputFrame]]): EsContainer = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy): EsContainer = { create( esVersion = esVersion, esConfig = esConfig, @@ -81,8 +82,8 @@ object EsContainerWithRorAndXpackSecurity extends StrictLogging { startedClusterDependencies = startedClusterDependencies, customEntrypoint = customEntrypoint, performPatching = false, - awaitingReadyStrategy = awaitingReadyStrategy, additionalLogConsumer = additionalLogConsumer, + awaitingReadyStrategy = awaitingReadyStrategy, ) } @@ -93,8 +94,8 @@ object EsContainerWithRorAndXpackSecurity extends StrictLogging { startedClusterDependencies: StartedClusterDependencies, customEntrypoint: Option[Path], performPatching: Boolean, - awaitingReadyStrategy: AwaitingReadyStrategy, - additionalLogConsumer: Option[Consumer[OutputFrame]]): EsContainer = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy): EsContainer = { new EsContainerWithRorAndXpackSecurity( esConfig, esVersion, diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithRorSecurity.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithRorSecurity.scala index 93e56c59a4..3196f0cb3b 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithRorSecurity.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithRorSecurity.scala @@ -52,7 +52,8 @@ object EsContainerWithRorSecurity extends StrictLogging { rorConfig: ReadonlyRestPlugin.Config, initializer: ElasticsearchNodeDataInitializer, startedClusterDependencies: StartedClusterDependencies, - additionalLogConsumer: Option[Consumer[OutputFrame]]): EsContainer = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy): EsContainer = { new EsContainerWithRorSecurity( esVersion, esConfig, @@ -64,6 +65,7 @@ object EsContainerWithRorSecurity extends StrictLogging { }, initializer, additionalLogConsumer, + awaitingReadyStrategy ) } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithXpackSecurity.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithXpackSecurity.scala index f4bcb99aa1..6d94138f93 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithXpackSecurity.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/EsContainerWithXpackSecurity.scala @@ -49,7 +49,8 @@ object EsContainerWithXpackSecurity extends StrictLogging { xpackSecurityConfig: XpackSecurityPlugin.Config, initializer: ElasticsearchNodeDataInitializer, startedClusterDependencies: StartedClusterDependencies, - additionalLogConsumer: Option[Consumer[OutputFrame]]): EsContainer = { + additionalLogConsumer: Option[Consumer[OutputFrame]], + awaitingReadyStrategy: AwaitingReadyStrategy): EsContainer = { new EsContainerWithXpackSecurity( esVersion, esConfig, @@ -58,6 +59,7 @@ object EsContainerWithXpackSecurity extends StrictLogging { xpackSecurityConfig.attributes.restSslEnabled, initializer, additionalLogConsumer, + awaitingReadyStrategy ) } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/LdapWithDnsContainer.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/LdapWithDnsContainer.scala index eac59c9710..ae46ba87ac 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/LdapWithDnsContainer.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/LdapWithDnsContainer.scala @@ -26,11 +26,13 @@ class LdapWithDnsContainer(name: String, ldapInitScript: InitScriptSource) private var dnsContainer: Option[DnsServerContainer] = None + def dnsHost: String = dnsContainer.getOrElse(throw new Exception("DNS container hasn't been started yet")).dnsHost + def dnsPort: Int = dnsContainer.getOrElse(throw new Exception("DNS container hasn't been started yet")).dnsPort override def start(): Unit = { ldapContainer.start() - dnsContainer = Option(new DnsServerContainer(ldapContainer.ldapPort)) + dnsContainer = Option(new DnsServerContainer(ldapContainer.ldapHost, ldapContainer.ldapPort)) dnsContainer.foreach(_.start()) } @@ -38,5 +40,4 @@ class LdapWithDnsContainer(name: String, ldapInitScript: InitScriptSource) ldapContainer.stop() dnsContainer.foreach(_.stop()) } - } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/OpenLdapContainer.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/OpenLdapContainer.scala index e687e98ea7..eeb7d427ef 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/OpenLdapContainer.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/OpenLdapContainer.scala @@ -16,7 +16,6 @@ */ package tech.beshu.ror.utils.containers -import better.files.Dispose import better.files.Dispose.FlatMap.Implicits import com.dimafeng.testcontainers.GenericContainer import com.typesafe.scalalogging.LazyLogging @@ -24,7 +23,7 @@ import com.unboundid.ldap.sdk.{LDAPConnection, ResultCode} import monix.eval.Task import monix.execution.Scheduler.Implicits.global import org.testcontainers.containers.Network -import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy +import org.testcontainers.containers.wait.strategy.{AbstractWaitStrategy, HostPortWaitStrategy} import tech.beshu.ror.utils.containers.LdapContainer.{InitScriptSource, defaults, initLdap} import tech.beshu.ror.utils.containers.LdapWaitStrategy.* import tech.beshu.ror.utils.misc.ScalaUtils.* @@ -96,12 +95,18 @@ object NonStoppableOpenLdapContainer { private class LdapWaitStrategy(name: String, ldapInitScript: InitScriptSource) - extends HostPortWaitStrategy() + extends AbstractWaitStrategy with LazyLogging with Implicits { + private val underlying = HostPortWaitStrategy().forPorts(OpenLdapContainer.port, OpenLdapContainer.sslPort) + override def waitUntilReady(): Unit = { - super.waitUntilReady() + underlying.waitUntilReady(this.waitStrategyTarget) + waitForLdapInitialization() + } + + private def waitForLdapInitialization(): Unit = { logger.info(s"Waiting for LDAP container '$name' ...") retryBackoff(ldapInitiate(), 15, 1 second, 1) .onErrorHandle { ex => @@ -113,12 +118,12 @@ private class LdapWaitStrategy(name: String, } private def ldapInitiate() = { - runOnBindedLdapConnection { connection => + runOnBoundLdapConnection { connection => initLdap(connection, ldapInitScript) } } - private def runOnBindedLdapConnection(action: LDAPConnection => Unit): Task[Unit] = { + private def runOnBoundLdapConnection(action: LDAPConnection => Unit): Task[Unit] = { defaults.ldap.bindDn match { case Some(bindDn) => Task(new LDAPConnection(waitStrategyTarget.getHost, waitStrategyTarget.getMappedPort(OpenLdapContainer.port))) diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/RorConfigAdjuster.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/RorConfigAdjuster.scala deleted file mode 100644 index 6fa86ce9b7..0000000000 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/RorConfigAdjuster.scala +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file is part of ReadonlyREST. - * - * ReadonlyREST is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * ReadonlyREST is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ - */ -package tech.beshu.ror.utils.containers - -import better.files.File -import tech.beshu.ror.utils.containers.ContainerOps.* -import tech.beshu.ror.utils.misc.OsUtils -import tech.beshu.ror.utils.misc.OsUtils.CurrentOs - -object RorConfigAdjuster { - - private val hostPlaceholder = "HOST" - private val portPlaceholder = "PORT" - - final case class Replacement(host: String, port: Int) - - def adjustUsingDependencies(config: String, - startedDependencies: StartedClusterDependencies): String = { - startedDependencies.values - .foldLeft(config)(replacePlaceholder) - } - - def adjustUsingDependencies(source: File, - startedDependencies: StartedClusterDependencies): File = { - val configWithResolvedDependencies = startedDependencies.values - .foldLeft(source.contentAsString)(replacePlaceholder) - - createTempFile.overwrite(configWithResolvedDependencies) - } - - private def replacePlaceholder(fileContent: String, - dependency: StartedDependency): String = { - val replacement = resolveReplacementForGivenMode(dependency) - fileContent - .replaceAll(s"\\{${dependency.name}_$hostPlaceholder\\}", replacement.host) - .replaceAll(s"\\{${dependency.name}_$portPlaceholder\\}", replacement.port.toString) - } - - private def resolveReplacementForGivenMode(dependency: StartedDependency): Replacement = { - Replacement( - host = OsUtils.currentOs match { - case CurrentOs.Windows => - "localhost" - case CurrentOs.OtherThanWindows => - dependency.container.ipAddressFromFirstNetwork.getOrElse(throw new IllegalStateException("Could not extract ip address inside docker network")) - }, - port = dependency.originalPort - ) - } - - private def createTempFile = File.newTemporaryFile("tmp", ".tmp").deleteOnExit() -} diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/RorSettingsAdjuster.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/RorSettingsAdjuster.scala new file mode 100644 index 0000000000..ebee6784ed --- /dev/null +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/RorSettingsAdjuster.scala @@ -0,0 +1,66 @@ +/* + * This file is part of ReadonlyREST. + * + * ReadonlyREST is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ReadonlyREST is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ReadonlyREST. If not, see http://www.gnu.org/licenses/ + */ +package tech.beshu.ror.utils.containers + +import better.files.File +import tech.beshu.ror.utils.containers.ContainerOps.* +import tech.beshu.ror.utils.misc.OsUtils +import tech.beshu.ror.utils.misc.OsUtils.CurrentOs + +object RorSettingsAdjuster { + + private val hostPlaceholder = "HOST" + private val portPlaceholder = "PORT" + + final case class Replacement(host: String, port: Int) + + def adjustUsingDependencies(settings: String, + startedDependencies: StartedClusterDependencies): String = { + startedDependencies.values + .foldLeft(settings)(replacePlaceholder) + } + + def adjustUsingDependencies(source: File, + startedDependencies: StartedClusterDependencies): File = { + val settingsWithResolvedDependencies = startedDependencies.values + .foldLeft(source.contentAsString)(replacePlaceholder) + + createTempFile.overwrite(settingsWithResolvedDependencies) + } + + private def replacePlaceholder(fileContent: String, + dependency: StartedDependency): String = { + val replacement = resolveReplacementForGivenMode(dependency) + fileContent + .replaceAll(s"\\{${dependency.name}_$hostPlaceholder\\}", replacement.host) + .replaceAll(s"\\{${dependency.name}_$portPlaceholder\\}", replacement.port.toString) + } + + private def resolveReplacementForGivenMode(dependency: StartedDependency): Replacement = { + Replacement( + host = OsUtils.currentOs match { + case CurrentOs.Windows => + "localhost" + case CurrentOs.OtherThanWindows => + dependency.container.ipAddressFromFirstNetwork.getOrElse(throw new IllegalStateException("Could not extract ip address inside docker network")) + }, + port = dependency.originalPort + ) + } + + private def createTempFile = File.newTemporaryFile("tmp", ".tmp").deleteOnExit() +} diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/SingletonEsContainerWithRorSecurity.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/SingletonEsContainerWithRorSecurity.scala index 445c115b4d..35f6de70dc 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/SingletonEsContainerWithRorSecurity.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/SingletonEsContainerWithRorSecurity.scala @@ -53,10 +53,10 @@ object SingletonEsContainerWithRorSecurity logOnFailure(snapshotManager.deleteAllRepositories().force()) } - def updateConfig(rorConfig: String): Unit = { + def updateSettings(rorSettings: String): Unit = { rorApiManager - .updateRorInIndexConfig(rorConfig) - .forceOKStatusOrConfigAlreadyLoaded() + .updateRorInIndexSettings(rorSettings) + .forceOKStatusOrSettingsAlreadyLoaded() } def initNode(nodeDataInitializer: ElasticsearchNodeDataInitializer): Unit = { diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/ToxiproxyContainer.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/ToxiproxyContainer.scala index 86e2e6f127..ad682ca0b3 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/ToxiproxyContainer.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/ToxiproxyContainer.scala @@ -17,26 +17,35 @@ package tech.beshu.ror.utils.containers import com.dimafeng.testcontainers.{GenericContainer, SingleContainer} +import com.typesafe.scalalogging.LazyLogging import eu.rekawek.toxiproxy.model.{ToxicDirection, toxic} import eu.rekawek.toxiproxy.{Proxy, ToxiproxyClient} +import monix.eval.Task +import monix.execution.Scheduler.Implicits.global import org.testcontainers.containers.Network -import tech.beshu.ror.utils.containers.ToxiproxyContainer.{ToxiproxyWaitStrategy, httpApiPort, proxiedPort} +import org.testcontainers.containers.wait.strategy.{WaitStrategy, WaitStrategyTarget} +import tech.beshu.ror.utils.containers.ToxiproxyContainer.{httpApiPort, proxiedPort} +import tech.beshu.ror.utils.misc.ScalaUtils.* +import java.time.Duration import scala.concurrent.duration.* import scala.language.postfixOps class ToxiproxyContainer[T <: SingleContainer[_]](val innerContainer: T, innerServicePort: Int) extends GenericContainer( - dockerImage = "ghcr.io/shopify/toxiproxy:2.12.0", + dockerImage = "shopify/toxiproxy:2.1.4", exposedPorts = Seq(httpApiPort, proxiedPort), - waitStrategy = Some(new ToxiproxyWaitStrategy(innerContainer, innerServicePort)) - ) { + waitStrategy = Some(new ToxiproxyApiWaitStrategy()) + ) with LazyLogging { container.setNetwork(Network.SHARED) + container.withStartupTimeout(Duration.ofSeconds(120)) private var innerContainerProxy: Option[Proxy] = None private var timeoutToxic: Option[toxic.Timeout] = None + def containerHost: String = container.getHost + def innerContainerMappedPort: Int = container.getMappedPort(proxiedPort) def disableNetwork(): Unit = { @@ -62,8 +71,7 @@ class ToxiproxyContainer[T <: SingleContainer[_]](val innerContainer: T, innerSe innerContainer.start() super.start() - val toxiproxyClient = new ToxiproxyClient(container.getHost, container.getMappedPort(httpApiPort)) - innerContainerProxy = Some(toxiproxyClient.getProxy("proxy")) + innerContainerProxy = Some(createProxy()) } override def stop(): Unit = { @@ -71,24 +79,67 @@ class ToxiproxyContainer[T <: SingleContainer[_]](val innerContainer: T, innerSe super.stop() } + private def createProxy() = { + // Create proxy AFTER both containers are fully started + // Fetch fresh container info to get the correct IP address + val dockerClient = container.getDockerClient + val freshContainerInfo = dockerClient.inspectContainerCmd(innerContainer.containerId).exec() + val innerNetworks = freshContainerInfo.getNetworkSettings.getNetworks + + val innerContainerIp = + if (innerNetworks.isEmpty) innerContainer.containerInfo.getConfig.getHostName + else innerNetworks.values().iterator().next().getIpAddress + + val proxyUpstream = s"$innerContainerIp:$innerServicePort" + val proxyListen = s"0.0.0.0:$proxiedPort" + + logger.debug(s"[TOXIPROXY] Creating proxy: listen=$proxyListen, upstream=$proxyUpstream (container IP)") + val toxiproxyClient = new ToxiproxyClient(container.getHost, container.getMappedPort(httpApiPort)) + val proxy = toxiproxyClient.createProxy("proxy", proxyListen, proxyUpstream) + logger.debug(s"[TOXIPROXY] Proxy created successfully") + + proxy + } } -object ToxiproxyContainer { +private class ToxiproxyApiWaitStrategy extends WaitStrategy with LazyLogging { - private class ToxiproxyWaitStrategy(innerContainer: SingleContainer[_], innerServicePort: Int) - extends WaitWithRetriesStrategy("toxiproxy") { - - override protected def isReady: Boolean = { - try { - val toxiproxyClient = new ToxiproxyClient(waitStrategyTarget.getHost, waitStrategyTarget.getMappedPort(httpApiPort)) - toxiproxyClient.createProxy("proxy", s"[::]:$proxiedPort", s"${innerContainer.containerInfo.getConfig.getHostName}:$innerServicePort") - true - } catch { - case _: Exception => false - } + private var startupTimeout: Duration = Duration.ofSeconds(60) + + override def waitUntilReady(waitStrategyTarget: WaitStrategyTarget): Unit = { + val host = waitStrategyTarget.getHost + val port = waitStrategyTarget.getMappedPort(ToxiproxyContainer.httpApiPort) + + logger.debug(s"[TOXIPROXY_WAIT] Waiting for Toxiproxy API at $host:$port") + + retryBackoff( + source = Task.delay(isToxiproxyReady(host, port)), + maxRetries = 150, + firstDelay = 1 second, + backOffScaler = 1 + ).runSyncUnsafe(startupTimeout) + } + + private def isToxiproxyReady(host: String, port: Int): Boolean = { + try { + val client = new ToxiproxyClient(host, port) + client.getProxies + logger.debug(s"[TOXIPROXY_WAIT] Toxiproxy API is ready at $host:$port") + true + } catch { + case e: Exception => + logger.debug(s"[TOXIPROXY_WAIT] Toxiproxy API not ready yet: ${e.getMessage}") + false } } - private val httpApiPort = 8474 + override def withStartupTimeout(startupTimeout: Duration): WaitStrategy = { + this.startupTimeout = startupTimeout + this + } +} + +object ToxiproxyContainer { + private[containers] val httpApiPort = 8474 val proxiedPort = 5000 } \ No newline at end of file diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/WireMockContainer.java b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/WireMockContainer.java index 464a788ed8..5140dbe6dd 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/WireMockContainer.java +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/WireMockContainer.java @@ -47,8 +47,8 @@ private WireMockContainer(ImageFromDockerfile imageFromDockerfile) { public static WireMockContainer create(String... mappings) { ImageFromDockerfile dockerfile = new ImageFromDockerfile(); List mappingFiles = Lists.newArrayList(mappings).stream() - .map(ContainerUtils::getResourceFile) - .collect(Collectors.toList()); + .map(ContainerUtils::getResourceFile) + .collect(Collectors.toList()); mappingFiles.forEach(mappingFile -> dockerfile.withFileFromFile(mappingFile.getName(), mappingFile)); logger.info("Creating WireMock container ..."); WireMockContainer container = new WireMockContainer( @@ -61,7 +61,7 @@ public static WireMockContainer create(String... mappings) { .withExposedPorts(WIRE_MOCK_PORT) .waitingFor( container.waitStrategy() - .withStartupTimeout(CONTAINER_STARTUP_TIMEOUT) + .withStartupTimeout(CONTAINER_STARTUP_TIMEOUT) ); cont.setNetwork(Network.SHARED); @@ -87,9 +87,10 @@ private WaitStrategy waitStrategy() { protected boolean isReady() { try { RestClient client = getClient(); - return client - .execute(new HttpGet(client.from("/__admin/"))) - .getStatusLine().getStatusCode() == 200; + return client.handle( + new HttpGet(client.from("/__admin/")), + r -> r.getStatusLine().getStatusCode() == 200 + ); } catch (Exception e) { return false; } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/images/ReadonlyRestPlugin.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/images/ReadonlyRestPlugin.scala index 95998b4b92..8555e1c984 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/images/ReadonlyRestPlugin.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/images/ReadonlyRestPlugin.scala @@ -29,24 +29,24 @@ import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.language.postfixOps object ReadonlyRestPlugin { - final case class Config(rorConfig: File, + final case class Config(rorSettings: File, rorPlugin: File, attributes: Attributes) object Config { - final case class Attributes(rorConfigReloading: Enabled[FiniteDuration], - rorInIndexConfigLoadingDelay: FiniteDuration, + final case class Attributes(rorSettingsReloading: Enabled[FiniteDuration], + rorInIndexSettingsLoadingDelay: FiniteDuration, rorCustomSettingsIndex: Option[String], restSsl: Enabled[RestSsl], internodeSsl: Enabled[InternodeSsl], - rorConfigFileName: String) + rorSettingsFileName: String) object Attributes { val default: Attributes = Attributes( - rorConfigReloading = Enabled.No, - rorInIndexConfigLoadingDelay = 0 seconds, + rorSettingsReloading = Enabled.No, + rorInIndexSettingsLoadingDelay = 0 seconds, rorCustomSettingsIndex = None, restSsl = Enabled.Yes(RestSsl.Ror(SourceFile.EsFile)), internodeSsl = Enabled.No, - rorConfigFileName = "/basic/readonlyrest.yml" + rorSettingsFileName = "/basic/readonlyrest.yml" ) } @@ -77,7 +77,7 @@ class ReadonlyRestPlugin(esVersion: String, .copyFile(esConfig.esConfigDir / "elastic-certificates-cert.pem", fromResourceBy(name = "elastic-certificates-cert.pem")) .copyFile(esConfig.esConfigDir / "elastic-certificates-pkey.pem", fromResourceBy(name = "elastic-certificates-pkey.pem")) .updateFipsDependencies(esConfig) - .copyFile(esConfig.esConfigDir / "readonlyrest.yml", config.rorConfig) + .copyFile(esConfig.esConfigDir / "readonlyrest.yml", config.rorSettings) .installRorPlugin(esConfig) .when(performPatching, _.patchES(esConfig)) } @@ -85,7 +85,7 @@ class ReadonlyRestPlugin(esVersion: String, override def updateEsConfigBuilder(builder: EsConfigBuilder): EsConfigBuilder = { builder .add("xpack.security.enabled: false") - .configureRorConfigAutoReloading() + .configureRorSettingsAutoReloading() .configureRorCustomIndexSettings() .configureRestSsl() .configureTransportSsl() @@ -102,7 +102,7 @@ class ReadonlyRestPlugin(esVersion: String, s"-Dcom.unboundid.ldap.sdk.debug.enabled=${if (enabled) true else false}" private def rorReloadingInterval() = { - val interval = config.attributes.rorConfigReloading match { + val interval = config.attributes.rorSettingsReloading match { case Enabled.No => "0sec" case Enabled.Yes(interval: FiniteDuration) => s"${interval.toMillis.toInt}ms" } @@ -111,7 +111,7 @@ class ReadonlyRestPlugin(esVersion: String, private def addLoadingSettings() = { Seq( - s"-Dcom.readonlyrest.settings.loading.delay=${config.attributes.rorInIndexConfigLoadingDelay.toMillis}ms", + s"-Dcom.readonlyrest.settings.loading.delay=${config.attributes.rorInIndexSettingsLoadingDelay.toMillis}ms", s"-Dcom.readonlyrest.settings.loading.attempts.count=1", s"-Dcom.readonlyrest.settings.loading.attempts.interval=0sec" ) @@ -187,8 +187,8 @@ class ReadonlyRestPlugin(esVersion: String, private implicit class ConfigureRorConfigReloading(val builder: EsConfigBuilder) { - def configureRorConfigAutoReloading(): EsConfigBuilder = { - config.attributes.rorConfigReloading match { + def configureRorSettingsAutoReloading(): EsConfigBuilder = { + config.attributes.rorSettingsReloading match { case Enabled.Yes(_) => builder case Enabled.No => diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/images/ReadonlyRestWithEnabledXpackSecurityPlugin.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/images/ReadonlyRestWithEnabledXpackSecurityPlugin.scala index 45d52e2cd6..7ec30df43a 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/images/ReadonlyRestWithEnabledXpackSecurityPlugin.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/images/ReadonlyRestWithEnabledXpackSecurityPlugin.scala @@ -26,24 +26,24 @@ import tech.beshu.ror.utils.containers.images.domain.Enabled import scala.concurrent.duration.FiniteDuration object ReadonlyRestWithEnabledXpackSecurityPlugin { - final case class Config(rorConfig: File, + final case class Config(rorSettings: File, rorPlugin: File, attributes: Attributes) object Config { - final case class Attributes(rorConfigReloading: Enabled[FiniteDuration], - rorInIndexConfigLoadingDelay: FiniteDuration, + final case class Attributes(rorSettingsReloading: Enabled[FiniteDuration], + rorInIndexSettingsLoadingDelay: FiniteDuration, rorCustomSettingsIndex: Option[String], restSsl: Enabled[RestSsl], internodeSsl: Enabled[InternodeSsl], - rorConfigFileName: String) + rorSettingsFileName: String) object Attributes { val default: Attributes = Attributes( - rorConfigReloading = ReadonlyRestPlugin.Config.Attributes.default.rorConfigReloading, - rorInIndexConfigLoadingDelay = ReadonlyRestPlugin.Config.Attributes.default.rorInIndexConfigLoadingDelay, + rorSettingsReloading = ReadonlyRestPlugin.Config.Attributes.default.rorSettingsReloading, + rorInIndexSettingsLoadingDelay = ReadonlyRestPlugin.Config.Attributes.default.rorInIndexSettingsLoadingDelay, rorCustomSettingsIndex = ReadonlyRestPlugin.Config.Attributes.default.rorCustomSettingsIndex, restSsl = if(XpackSecurityPlugin.Config.Attributes.default.restSslEnabled) Enabled.Yes(RestSsl.Xpack) else Enabled.No, internodeSsl = if(XpackSecurityPlugin.Config.Attributes.default.internodeSslEnabled) Enabled.Yes(InternodeSsl.Xpack) else Enabled.No, - rorConfigFileName = "/basic/readonlyrest.yml" + rorSettingsFileName = "/basic/readonlyrest.yml" ) } @@ -93,15 +93,15 @@ class ReadonlyRestWithEnabledXpackSecurityPlugin(esVersion: String, private def createRorConfig() = { ReadonlyRestPlugin.Config( - rorConfig = config.rorConfig, + rorSettings = config.rorSettings, rorPlugin = config.rorPlugin, attributes = ReadonlyRestPlugin.Config.Attributes( - config.attributes.rorConfigReloading, - config.attributes.rorInIndexConfigLoadingDelay, + config.attributes.rorSettingsReloading, + config.attributes.rorInIndexSettingsLoadingDelay, config.attributes.rorCustomSettingsIndex, restSsl = createRorRestSsl(), internodeSsl = createRorInternodeSsl(), - config.attributes.rorConfigFileName + config.attributes.rorSettingsFileName ) ) } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/providers.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/providers.scala index 3d4dad13e3..ba39588c20 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/providers.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/providers.scala @@ -48,12 +48,12 @@ object providers { private[providers] def client(credentials: Credentials): RestClient } - trait RorConfigFileNameProvider { - implicit def rorConfigFileName: String + trait RorSettingsFileNameProvider { + implicit def rorSettingsFileName: String } - trait ResolvedRorConfigFileProvider { - def resolvedRorConfigFile: File + trait ResolvedRorSettingsFileProvider { + def resolvedRorSettingsFile: File } trait NodeInitializerProvider { diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/windows/WindowsEsPortProvider.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/windows/WindowsEsPortProvider.scala index 2ee338db09..57fbb2dba4 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/windows/WindowsEsPortProvider.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/containers/windows/WindowsEsPortProvider.scala @@ -61,6 +61,9 @@ object WindowsEsPortProvider { "ror_xpack_cluster_3", "testEsCluster_1", "testEsCluster_2", + "startingTest_EsCluster_1_1", + "startingTest_EsCluster_2_1", + "startingTest_EsCluster_3_1", ).map(nodeName => (nodeName, nextPorts)) ) diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/elasticsearch/BaseManager.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/elasticsearch/BaseManager.scala index 7abf341db3..c2b616b12e 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/elasticsearch/BaseManager.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/elasticsearch/BaseManager.scala @@ -27,7 +27,6 @@ import tech.beshu.ror.utils.elasticsearch.BaseManager.{JSON, SimpleHeader} import tech.beshu.ror.utils.httpclient.HttpResponseHelper.stringBodyFrom import tech.beshu.ror.utils.httpclient.RestClient import tech.beshu.ror.utils.misc.OsUtils.CurrentOs -import tech.beshu.ror.utils.misc.ScalaUtils.* import tech.beshu.ror.utils.misc.{OsUtils, Version} import java.time.Duration @@ -42,14 +41,15 @@ abstract class BaseManager(client: RestClient, protected def call[T <: SimpleResponse](request: HttpUriRequest, fromResponse: HttpResponse => T): T = { client - .execute { - additionalHeaders.foldLeft(request) { + .handle( + request = additionalHeaders.foldLeft(request) { case (req, (name, value)) => req.addHeader(name, value) req } - } - .bracket(fromResponse) + )( + fromResponse = fromResponse + ) } protected[elasticsearch] def eventually[T <: SimpleResponse](action: => T) @@ -93,6 +93,13 @@ abstract class BaseManager(client: RestClient, this } + def successOrNotFound(): this.type = { + if (!(isSuccess || isNotFound)) throw new IllegalStateException( + s"Expected success or not found but got HTTP $responseCode, body: $body" + ) + this + } + override def toString: String = response.toString private def checkResponseAssertions(): Unit = { diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/elasticsearch/RorApiManager.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/elasticsearch/RorApiManager.scala index e882878597..b5494a0fb6 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/elasticsearch/RorApiManager.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/elasticsearch/RorApiManager.scala @@ -44,48 +44,44 @@ class RorApiManager(client: RestClient, call(createSendAuditEventRequest(payload), new RorApiJsonResponse(_)) } - def getRorFileConfig: RorApiJsonResponse = { - call(createGetRorFileConfigRequest(), new RorApiJsonResponse(_)) + def getRorFileSettings: RorApiJsonResponse = { + call(createGetRorFileSettingsRequest(), new RorApiJsonResponse(_)) } - def getRorInIndexConfig: RorApiJsonResponse = { - call(createGetRorInIndexConfigRequest(), new RorApiJsonResponse(_)) + def getRorInIndexSettings: RorApiJsonResponse = { + call(createGetRorInIndexSettingsRequest(), new RorApiJsonResponse(_)) } - def loadRorCurrentConfig(additionalParams: Map[String, String] = Map.empty): RorApiJsonResponse = { - call(createLoadRorCurrentConfigRequest(additionalParams), new RorApiJsonResponse(_)) + def updateRorInIndexSettings(settings: String): RorApiResponseWithBusinessStatus = { + call(createUpdateRorInIndexSettingsRequest(settings), new RorApiResponseWithBusinessStatus(_)) } - def updateRorInIndexConfig(config: String): RorApiResponseWithBusinessStatus = { - call(createUpdateRorInIndexConfigRequest(config), new RorApiResponseWithBusinessStatus(_)) + def updateRorInIndexSettingsRaw(rawRequestBody: String): RorApiResponseWithBusinessStatus = { + call(createUpdateRorInIndexSettingsRequestFromRaw(rawRequestBody), new RorApiResponseWithBusinessStatus(_)) } - def updateRorInIndexConfigRaw(rawRequestBody: String): RorApiResponseWithBusinessStatus = { - call(createUpdateRorInIndexConfigRequestFromRaw(rawRequestBody), new RorApiResponseWithBusinessStatus(_)) + def currentRorTestSettings: RorApiJsonResponse = { + call(createGetTestSettingsRequest, new RorApiJsonResponse(_)) } - def currentRorTestConfig: RorApiJsonResponse = { - call(createGetTestConfigRequest, new RorApiJsonResponse(_)) + def updateRorTestSettings(settings: String, ttl: FiniteDuration = 30.minutes): RorApiResponseWithBusinessStatus = { + call(createUpdateRorTestSettingsRequest(settings, ttl), new RorApiResponseWithBusinessStatus(_)) } - def updateRorTestConfig(config: String, ttl: FiniteDuration = 30.minutes): RorApiResponseWithBusinessStatus = { - call(createUpdateRorTestConfigRequest(config, ttl), new RorApiResponseWithBusinessStatus(_)) + def updateRorTestSettingsRaw(rawRequestBody: String): RorApiResponseWithBusinessStatus = { + call(createUpdateRorTestSettingsRequest(rawRequestBody), new RorApiResponseWithBusinessStatus(_)) } - def updateRorTestConfigRaw(rawRequestBody: String): RorApiResponseWithBusinessStatus = { - call(createUpdateRorTestConfigRequest(rawRequestBody), new RorApiResponseWithBusinessStatus(_)) - } - - def invalidateRorTestConfig(): RorApiResponseWithBusinessStatus = { - call(createInvalidateRorTestConfigRequest(), new RorApiResponseWithBusinessStatus(_)) + def invalidateRorTestSettings(): RorApiResponseWithBusinessStatus = { + call(createInvalidateRorTestSettingsRequest(), new RorApiResponseWithBusinessStatus(_)) } def currentRorLocalUsers: RorApiJsonResponse = { call(createProvideLocalUsersRequest(), new RorApiJsonResponse(_)) } - def reloadRorConfig(): RorApiJsonResponse = { - call(createReloadRorConfigRequest(), new RorApiJsonResponse(_)) + def reloadRorSettings(): RorApiJsonResponse = { + call(createReloadRorSettingsRequest(), new RorApiJsonResponse(_)) } def configureImpersonationMocks(payload: JSON): RorApiResponseWithBusinessStatus = { @@ -107,10 +103,10 @@ class RorApiManager(client: RestClient, call(createConfigureImpersonationMocksRequest(payload), new RorApiResponseWithBusinessStatus(_)) } - def insertInIndexConfigDirectlyToRorIndex(rorConfigIndex: String, config: String): documentManager.JsonResponse = { + def insertInIndexSettingsDirectlyToRorIndex(rorIndex: String, settings: String): documentManager.JsonResponse = { documentManager.createFirstDoc( - index = rorConfigIndex, - content = ujson.read(rorConfigIndexDocumentContentFrom(config)) + index = rorIndex, + content = ujson.read(rorSettingsIndexDocumentContentFrom(settings)) ) } @@ -131,34 +127,34 @@ class RorApiManager(client: RestClient, request } - private def createUpdateRorInIndexConfigRequest(config: String) = { + private def createUpdateRorInIndexSettingsRequest(settings: String) = { val request = new HttpPost(client.from("/_readonlyrest/admin/config")) request.addHeader("Content-Type", "application/json") - request.setEntity(new StringEntity(rorConfigIndexDocumentContentFrom(config))) + request.setEntity(new StringEntity(rorSettingsIndexDocumentContentFrom(settings))) request } - private def createUpdateRorInIndexConfigRequestFromRaw(rawRequestJson: String) = { + private def createUpdateRorInIndexSettingsRequestFromRaw(rawRequestJson: String) = { val request = new HttpPost(client.from("/_readonlyrest/admin/config")) request.addHeader("Content-Type", "application/json") request.setEntity(new StringEntity(rawRequestJson)) request } - private def createGetTestConfigRequest = { + private def createGetTestSettingsRequest = { new HttpGet(client.from("/_readonlyrest/admin/config/test")) } - private def createUpdateRorTestConfigRequest(config: String, - ttl: FiniteDuration) = { + private def createUpdateRorTestSettingsRequest(settings: String, + ttl: FiniteDuration) = { val request = new HttpPost(client.from("/_readonlyrest/admin/config/test")) request.addHeader("Content-Type", "application/json") - request.setEntity(new StringEntity(rorTestConfig(config, ttl))) + request.setEntity(new StringEntity(rorTestSettings(settings, ttl))) request } - private def createUpdateRorTestConfigRequest(rawRequestJson: String) = { + private def createUpdateRorTestSettingsRequest(rawRequestJson: String) = { val request = new HttpPost(client.from("/_readonlyrest/admin/config/test")) request.addHeader("Content-Type", "application/json") @@ -166,15 +162,15 @@ class RorApiManager(client: RestClient, request } - private def rorConfigIndexDocumentContentFrom(config: String) = { - s"""{"settings": "${escapeJava(config)}"}""" + private def rorSettingsIndexDocumentContentFrom(settings: String) = { + s"""{"settings": "${escapeJava(settings)}"}""" } - private def rorTestConfig(config: String, ttl: FiniteDuration) = { - s"""{"settings": "${escapeJava(config)}", "ttl": "${ttl.toString()}"}""" + private def rorTestSettings(settings: String, ttl: FiniteDuration) = { + s"""{"settings": "${escapeJava(settings)}", "ttl": "${ttl.toString()}"}""" } - private def createInvalidateRorTestConfigRequest() = { + private def createInvalidateRorTestSettingsRequest() = { new HttpDelete(client.from("/_readonlyrest/admin/config/test")) } @@ -182,15 +178,15 @@ class RorApiManager(client: RestClient, new HttpGet(client.from("/_readonlyrest/admin/config/test/localusers")) } - private def createGetRorFileConfigRequest() = { + private def createGetRorFileSettingsRequest() = { new HttpGet(client.from("/_readonlyrest/admin/config/file")) } - private def createGetRorInIndexConfigRequest() = { + private def createGetRorInIndexSettingsRequest() = { new HttpGet(client.from("/_readonlyrest/admin/config")) } - private def createReloadRorConfigRequest() = { + private def createReloadRorSettingsRequest() = { val request = new HttpPost(client.from("/_readonlyrest/admin/refreshconfig")) request.addHeader("Content-Type", "application/json") request @@ -207,10 +203,6 @@ class RorApiManager(client: RestClient, new HttpGet(client.from("/_readonlyrest/admin/config/test/authmock")) } - private def createLoadRorCurrentConfigRequest(additionalParams: Map[String, String]) = { - new HttpGet(client.from("/_readonlyrest/admin/config/load", additionalParams)) - } - final class RorApiJsonResponse(override val response: HttpResponse) extends JsonResponse(response) @@ -232,14 +224,14 @@ class RorApiManager(client: RestClient, this } - def forceOKStatusOrConfigAlreadyLoaded(): this.type = { + def forceOKStatusOrSettingsAlreadyLoaded(): this.type = { force() - if (businessStatus === "OK" || isConfigAlreadyLoaded) { + if (businessStatus === "OK" || isSettingsAlreadyLoaded) { this } else { throw new IllegalStateException( s""" - |Expected business status 'OK' or info about already loaded config, but got:" + |Expected business status 'OK' or info about already loaded settings, but got:" | |HTTP $responseCode |${responseJson.toString()} @@ -248,7 +240,7 @@ class RorApiManager(client: RestClient, } } - private def isConfigAlreadyLoaded = { + private def isSettingsAlreadyLoaded = { businessStatus == "KO" && message.contains("already loaded") } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/httpclient/RestClient.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/httpclient/RestClient.scala index 96011e2eec..f2ff250187 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/httpclient/RestClient.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/httpclient/RestClient.scala @@ -16,6 +16,7 @@ */ package tech.beshu.ror.utils.httpclient +import cats.effect.Resource import monix.eval.Task import monix.execution.Scheduler.Implicits.global import org.apache.http.auth.UsernamePasswordCredentials @@ -60,7 +61,27 @@ class RestClient(ssl: Boolean, def from(path: String): URI = buildUri(path, Map.empty) - def execute(req: HttpUriRequest): CloseableHttpResponse = { + def executeAsync(request: HttpUriRequest): Resource[Task, HttpResponse] = { + Resource + .make( + Task.delay(callExecute(request)) + )( + response => Task.delay(response.close()) + ) + } + + def executeSync[T](request: HttpUriRequest): Unit = { + handle(request)(identity) + } + + def handle[T](request: HttpUriRequest) + (fromResponse: HttpResponse => T): T = { + executeAsync(request) + .use(response => Task.delay(fromResponse(response))) + .runSyncUnsafe() + } + + private def callExecute(req: HttpUriRequest): CloseableHttpResponse = { Task(underlying.execute(req)) .onErrorRestartLoop(0) { case (ex: HttpHostConnectException, 10, _) => diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/misc/EsStartupChecker.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/misc/EsStartupChecker.scala index 5fe884a0ef..2067784065 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/misc/EsStartupChecker.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/misc/EsStartupChecker.scala @@ -16,11 +16,10 @@ */ package tech.beshu.ror.utils.misc -import cats.effect.Resource -import cats.implicits.* import com.typesafe.scalalogging.LazyLogging import monix.eval.Task import monix.execution.Scheduler.Implicits.global +import org.apache.http.HttpResponse import org.apache.http.client.methods.HttpGet import tech.beshu.ror.utils.httpclient.HttpResponseHelper.deserializeJsonBody import tech.beshu.ror.utils.httpclient.RestClient @@ -43,41 +42,52 @@ class EsStartupChecker private(name: String, } private def clusterIsReady(client: RestClient): Task[Unit] = { - Resource - .make( - Task - .delay(client.execute(new HttpGet(client.from("_cluster/health")))) - .recoverWith { ex => - logger.error(s"[$name] ES not ready yet, healthcheck failed") - Task.raiseError(ex) - } - )( - response => Task.delay(response.close()) - ) + client + .executeAsync(new HttpGet(client.from("_cluster/health"))) .use { response => - response.getStatusLine.getStatusCode match { - case 200 => - mode match { - case Mode.GreenCluster => - val healthJson = deserializeJsonBody(RestClient.bodyFrom(response)) - val healthStatus = healthJson.get("status") - if (healthStatus == "green") { - logger.info(s"[$name] ES is ready") - Task.unit - } else { - logger.info(s"[$name] ES not ready yet, health status is $healthStatus") - Task.raiseError(ClusterNotReady) - } - case Mode.Accessible => - logger.info(s"[$name] ES is ready") - Task.unit - } - case otherStatus => - logger.info(s"[$name] ES not ready yet, received HTTP $otherStatus") - Task.raiseError(ClusterNotReady) + val isOk = mode match { + case Mode.GreenCluster => isClusterGreen(response) + case Mode.Accessible => isClusterAccessible(response) + case Mode.Reachable => isClusterReachable(response) } + if(isOk) Task.unit + else Task.raiseError(ClusterNotReady) } } + + private def isClusterGreen(response: HttpResponse) = { + response.getStatusLine.getStatusCode match { + case 200 => + val healthJson = deserializeJsonBody(RestClient.bodyFrom(response)) + healthJson.get("status") match { + case "green" => + logger.info(s"[$name] ES is ready") + true + case healthStatus => + logger.info(s"[$name] ES not ready yet, health status is $healthStatus") + false + } + case otherStatus => + logger.info(s"[$name] ES not ready yet, received HTTP $otherStatus") + false + } + } + + private def isClusterAccessible(response: HttpResponse) = { + response.getStatusLine.getStatusCode match { + case 200 => + logger.info(s"[$name] ES is ready") + true + case otherStatus => + logger.info(s"[$name] ES not ready yet, received HTTP $otherStatus") + false + } + } + + private def isClusterReachable(response: HttpResponse) = { + logger.info(s"[$name] ES is reachable") + true + } } object EsStartupChecker { @@ -90,11 +100,14 @@ object EsStartupChecker { def accessibleEsChecker(name: String, client: RestClient): EsStartupChecker = new EsStartupChecker(name, client, Mode.Accessible) + def reachableEsChecker(name: String, client: RestClient): EsStartupChecker = + new EsStartupChecker(name, client, Mode.Reachable) + private sealed trait Mode private object Mode { case object GreenCluster extends Mode - case object Accessible extends Mode + case object Reachable extends Mode } } diff --git a/tests-utils/src/main/scala/tech/beshu/ror/utils/misc/ScalaUtils.scala b/tests-utils/src/main/scala/tech/beshu/ror/utils/misc/ScalaUtils.scala index f680af7729..0063a336fe 100644 --- a/tests-utils/src/main/scala/tech/beshu/ror/utils/misc/ScalaUtils.scala +++ b/tests-utils/src/main/scala/tech/beshu/ror/utils/misc/ScalaUtils.scala @@ -23,6 +23,7 @@ import com.typesafe.scalalogging.LazyLogging import monix.eval.Task import java.time.format.DateTimeFormatter +import java.util.concurrent.TimeUnit import scala.annotation.tailrec import scala.concurrent.duration.* import scala.language.{implicitConversions, postfixOps} @@ -76,6 +77,8 @@ object ScalaUtils extends LazyLogging { implicit def finiteDurationToJavaDuration(interval: FiniteDuration): Duration = Duration.ofMillis(interval.toMillis) + implicit def javaDurationToFiniteDuration(interval: Duration): FiniteDuration = FiniteDuration(interval.toMillis, TimeUnit.MILLISECONDS) + def retry(times: Int, cleanBeforeRetrying: => Unit = ())(action: => Unit): Unit = { @tailrec def loop(attempt: Int): Unit = {