From 77d92f7b4c9d7605d59471409a98a83255944c71 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Mon, 17 Mar 2025 17:21:14 -0400 Subject: [PATCH 01/15] Introduces resource sharing and access control SPI Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 240 ++++++++++--- .github/workflows/maven-publish.yml | 2 +- .gitignore | 4 - build.gradle | 92 +++-- scripts/build.sh | 4 + settings.gradle | 3 + spi/README.md | 167 +++++++++ spi/build.gradle | 86 +++++ .../security/spi/resources/Resource.java | 27 ++ .../spi/resources/ResourceAccessScope.java | 38 +++ .../spi/resources/ResourceParser.java | 29 ++ .../resources/ResourceSharingExtension.java | 35 ++ .../exceptions/ResourceSharingException.java | 60 ++++ .../security/spi/resources/package-info.java | 15 + .../spi/resources/sharing/CreatedBy.java | 88 +++++ .../spi/resources/sharing/Creator.java | 37 ++ .../spi/resources/sharing/Recipient.java | 31 ++ .../spi/resources/sharing/RecipientType.java | 24 ++ .../sharing/RecipientTypeRegistry.java | 39 +++ .../resources/sharing/ResourceSharing.java | 202 +++++++++++ .../spi/resources/sharing/ShareWith.java | 103 ++++++ .../resources/sharing/SharedWithScope.java | 169 +++++++++ .../spi/resources/CreatedByTests.java | 320 ++++++++++++++++++ .../resources/RecipientTypeRegistryTests.java | 43 +++ .../spi/resources/ShareWithTests.java | 284 ++++++++++++++++ .../security/OpenSearchSecurityPlugin.java | 17 +- .../security/support/ConfigConstants.java | 185 +++++----- 27 files changed, 2182 insertions(+), 162 deletions(-) create mode 100644 spi/README.md create mode 100644 spi/build.gradle create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/Resource.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/package-info.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/CreatedBy.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/Creator.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/Recipient.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientType.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientTypeRegistry.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/ShareWith.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithScope.java create mode 100644 spi/src/test/java/org/opensearch/security/spi/resources/CreatedByTests.java create mode 100644 spi/src/test/java/org/opensearch/security/spi/resources/RecipientTypeRegistryTests.java create mode 100644 spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 362bb78d69..6fcfda4926 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,9 +37,35 @@ jobs: run: | echo "separateTestsNames=$(./gradlew listTasksAsJSON -q --console=plain | tail -n 1)" >> $GITHUB_OUTPUT + publish-components-to-maven-local: + runs-on: ubuntu-latest + steps: + - name: Set up JDK for build and test + uses: actions/setup-java@v4 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: 21 + + - name: Checkout security + uses: actions/checkout@v4 + + - name: Publish components to Maven Local + run: | + ./gradlew clean \ + :opensearch-resource-sharing-spi:publishToMavenLocal \ + -Dbuild.snapshot=false + + - name: Cache artifacts for dependent jobs + uses: actions/cache@v4.2.2 + with: + path: ~/.m2/repository/org/opensearch/ + key: maven-local-${{ github.run_id }} + restore-keys: | + maven-local- + test-windows: name: test - needs: generate-test-list + needs: [generate-test-list, publish-components-to-maven-local] strategy: fail-fast: false matrix: @@ -101,6 +127,14 @@ jobs: - name: Checkout security uses: actions/checkout@v4 + - name: Restore Maven Local Cache + uses: actions/cache@v4.2.2 + with: + path: ~/.m2/repository/org/opensearch/ + key: maven-local-${{ github.run_id }} + restore-keys: | + maven-local- + - name: Build and Test uses: gradle/gradle-build-action@v3 with: @@ -116,7 +150,7 @@ jobs: ./build/reports/ report-coverage: - needs: ["test-windows", "test-linux", "integration-tests-windows", "integration-tests-linux"] + needs: ["test-windows", "test-linux", "integration-tests-windows", "integration-tests-linux", "spi-tests-linux", "spi-tests-windows"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -139,7 +173,6 @@ jobs: fail_ci_if_error: true verbose: true - integration-tests-windows: name: integration-tests strategy: @@ -159,12 +192,20 @@ jobs: - name: Checkout security uses: actions/checkout@v4 - - name: Build and Test + - name: Restore Maven Local Cache + uses: actions/cache@v4.2.2 + with: + path: ~/.m2/repository/org/opensearch/ + key: maven-local-${{ github.run_id }} + restore-keys: | + maven-local- + + - name: Run Integration Tests uses: gradle/gradle-build-action@v3 with: cache-disabled: true arguments: | - integrationTest -Dbuild.snapshot=false + :integrationTest -Dbuild.snapshot=false - uses: actions/upload-artifact@v4 if: always() @@ -215,9 +256,100 @@ jobs: path: | ./build/reports/ + spi-tests-linux: + name: spi-tests + needs: publish-components-to-maven-local + strategy: + fail-fast: false + matrix: + jdk: [21] + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + container: + # using the same image which is used by opensearch-build to build the OpenSearch Distribution + # this image tag is subject to change as more dependencies and updates will arrive over time + image: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-version-linux }} + # need to switch to root so that github actions can install runner binary on container without permission issues. + options: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-start-options }} + + steps: + - name: Set up JDK for build and test + uses: actions/setup-java@v4 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: ${{ matrix.jdk }} + + - name: Checkout security + uses: actions/checkout@v4 + + - name: Restore Maven Local Cache + uses: actions/cache@v4.2.2 + with: + path: ~/.m2/repository/org/opensearch/ + key: maven-local-${{ github.run_id }} + restore-keys: | + maven-local- + + - name: Run SPI Tests + uses: gradle/gradle-build-action@v3 + with: + cache-disabled: true + arguments: | + :opensearch-resource-sharing-spi:test -Dbuild.snapshot=false + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: spi-${{ matrix.platform }}-JDK${{ matrix.jdk }}-reports + path: | + ./build/reports/ + + spi-tests-windows: + name: spi-tests + needs: publish-components-to-maven-local + strategy: + fail-fast: false + matrix: + jdk: [21] + platform: [windows-latest] + runs-on: ${{ matrix.platform }} + + steps: + - name: Set up JDK for build and test + uses: actions/setup-java@v4 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: ${{ matrix.jdk }} + + - name: Checkout security + uses: actions/checkout@v4 + + - name: Restore Maven Local Cache + uses: actions/cache@v4.2.2 + with: + path: ~/.m2/repository/org/opensearch/ + key: maven-local-${{ github.run_id }} + restore-keys: | + maven-local- + + - name: Run SPI Tests + uses: gradle/gradle-build-action@v3 + with: + cache-disabled: true + arguments: | + :opensearch-resource-sharing-spi:test -Dbuild.snapshot=false + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: spi-${{ matrix.platform }}-JDK${{ matrix.jdk }}-reports + path: | + ./build/reports/ + resource-tests: env: CI_ENVIRONMENT: resource-test + needs: publish-components-to-maven-local strategy: fail-fast: false matrix: @@ -235,12 +367,20 @@ jobs: - name: Checkout security uses: actions/checkout@v4 - - name: Build and Test + - name: Restore Maven Local Cache + uses: actions/cache@v4.2.2 + with: + path: ~/.m2/repository/org/opensearch/ + key: maven-local-${{ github.run_id }} + restore-keys: | + maven-local- + + - name: Run Resource Tests uses: gradle/gradle-build-action@v3 with: cache-disabled: true arguments: | - integrationTest -Dbuild.snapshot=false --tests org.opensearch.security.ResourceFocusedTests + :integrationTest -Dbuild.snapshot=false --tests org.opensearch.security.ResourceFocusedTests backward-compatibility-build: runs-on: ubuntu-latest @@ -303,40 +443,62 @@ jobs: build-artifact-names: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Setup Environment + uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + - name: Configure Java + uses: actions/setup-java@v4 with: - distribution: temurin # Temurin is a distribution of adoptium + distribution: temurin java-version: 21 - - run: | - security_plugin_version=$(./gradlew properties -q | grep -E '^version:' | awk '{print $2}') - security_plugin_version_no_snapshot=$(echo $security_plugin_version | sed 's/-SNAPSHOT//g') - security_plugin_version_only_number=$(echo $security_plugin_version_no_snapshot | cut -d- -f1) - test_qualifier=alpha2 - - echo "SECURITY_PLUGIN_VERSION=$security_plugin_version" >> $GITHUB_ENV - echo "SECURITY_PLUGIN_VERSION_NO_SNAPSHOT=$security_plugin_version_no_snapshot" >> $GITHUB_ENV - echo "SECURITY_PLUGIN_VERSION_ONLY_NUMBER=$security_plugin_version_only_number" >> $GITHUB_ENV - echo "TEST_QUALIFIER=$test_qualifier" >> $GITHUB_ENV - - - run: | - echo ${{ env.SECURITY_PLUGIN_VERSION }} - echo ${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }} - echo ${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }} - echo ${{ env.TEST_QUALIFIER }} - - - run: ./gradlew clean assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip - - - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip - - - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip - - - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip - - - run: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.pom - - - name: List files in the build directory if there was an error - run: ls -al ./build/distributions/ + - name: Build and Test Artifacts + run: | + # Set version variables + security_plugin_version=$(./gradlew properties -q | grep -E '^version:' | awk '{print $2}') + security_plugin_version_no_snapshot=$(echo $security_plugin_version | sed 's/-SNAPSHOT//g') + security_plugin_version_only_number=$(echo $security_plugin_version_no_snapshot | cut -d- -f1) + test_qualifier=alpha2 + + # Debug print versions + echo "Versions:" + echo $security_plugin_version + echo $security_plugin_version_no_snapshot + echo $security_plugin_version_only_number + echo $test_qualifier + + # Publish SPI + ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version-all.jar + ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_no_snapshot-all.jar + ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false -Dbuild.version_qualifier=$test_qualifier && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier-all.jar + ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.version_qualifier=$test_qualifier && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier-SNAPSHOT-all.jar + + + # Build artifacts + ./gradlew clean assemble && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version.zip && \ + test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version.jar + + ./gradlew clean assemble -Dbuild.snapshot=false && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version_no_snapshot.zip && \ + test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_no_snapshot.jar + + ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=$test_qualifier && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version_only_number-$test_qualifier.zip && \ + test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier.jar + + ./gradlew clean assemble -Dbuild.version_qualifier=$test_qualifier && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version_only_number-$test_qualifier-SNAPSHOT.zip && \ + test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier-SNAPSHOT.jar + + ./gradlew clean publishPluginZipPublicationToZipStagingRepository && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version.zip && \ + test -s ./build/distributions/opensearch-security-$security_plugin_version.pom && \ + test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version-all.jar + + ./gradlew clean publishShadowPublicationToMavenLocal && \ + test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version-all.jar + + - name: List files in build directory on failure if: failure() + run: ls -al ./*/build/libs/ ./build/distributions/ diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index d10fd67beb..42d07fbb0a 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -32,4 +32,4 @@ jobs: export SONATYPE_PASSWORD=$(aws secretsmanager get-secret-value --secret-id maven-snapshots-password --query SecretString --output text) echo "::add-mask::$SONATYPE_USERNAME" echo "::add-mask::$SONATYPE_PASSWORD" - ./gradlew publishPluginZipPublicationToSnapshotsRepository + ./gradlew --no-daemon publishPluginZipPublicationToSnapshotsRepository publishShadowPublicationToSnapshotsRepository diff --git a/.gitignore b/.gitignore index 6fbfafabac..5eb2da999f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,3 @@ out/ build/ gradle-build/ .gradle/ - -# nodejs -node_modules/ -package-lock.json diff --git a/build.gradle b/build.gradle index c5a6d9d176..2113060e39 100644 --- a/build.gradle +++ b/build.gradle @@ -500,6 +500,12 @@ configurations { force "org.checkerframework:checker-qual:3.49.1" force "ch.qos.logback:logback-classic:1.5.17" force "commons-io:commons-io:2.18.0" + force "com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2" + force "org.hamcrest:hamcrest:2.2" + force "org.mockito:mockito-core:5.16.1" + force "net.bytebuddy:byte-buddy:1.15.11" + force "org.ow2.asm:asm:9.7.1" + force "com.google.j2objc:j2objc-annotations:3.0.0" } } @@ -507,6 +513,65 @@ configurations { integrationTestRuntimeOnly.extendsFrom runtimeOnly } +allprojects { + configurations { + integrationTestImplementation.extendsFrom implementation + compile.extendsFrom compileOnly + compile.extendsFrom testImplementation + } + dependencies { + // unit test framework + testImplementation 'org.hamcrest:hamcrest:2.2' + testImplementation 'junit:junit:4.13.2' + testImplementation "org.opensearch:opensearch:${opensearch_version}" + testImplementation "org.mockito:mockito-core:5.16.1" + + //integration test framework: + integrationTestImplementation('com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2') { + exclude(group: 'junit', module: 'junit') + } + integrationTestImplementation 'junit:junit:4.13.2' + integrationTestImplementation("org.opensearch.plugin:reindex-client:${opensearch_version}"){ + exclude(group: 'org.slf4j', module: 'slf4j-api') + } + integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}" + integrationTestImplementation 'commons-io:commons-io:2.18.0' + integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" + integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}" + integrationTestImplementation 'org.hamcrest:hamcrest:2.2' + integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}" + integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}" + integrationTestImplementation('org.awaitility:awaitility:4.2.2') { + exclude(group: 'org.hamcrest', module: 'hamcrest') + } + integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14' + integrationTestImplementation "org.opensearch.plugin:mapper-size:${opensearch_version}" + integrationTestImplementation "org.apache.httpcomponents:httpclient-cache:4.5.14" + integrationTestImplementation "org.apache.httpcomponents:httpclient:4.5.14" + integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14" + integrationTestImplementation "org.apache.httpcomponents:httpcore:4.4.16" + integrationTestImplementation "org.apache.httpcomponents:httpasyncclient:4.1.5" + integrationTestImplementation "org.mockito:mockito-core:5.16.1" + integrationTestImplementation "org.passay:passay:1.6.6" + integrationTestImplementation "org.opensearch:opensearch:${opensearch_version}" + integrationTestImplementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}" + integrationTestImplementation "org.opensearch.plugin:aggs-matrix-stats-client:${opensearch_version}" + integrationTestImplementation "org.opensearch.plugin:parent-join-client:${opensearch_version}" + integrationTestImplementation 'com.password4j:password4j:1.8.2' + integrationTestImplementation "com.google.guava:guava:${guava_version}" + integrationTestImplementation "org.apache.commons:commons-lang3:${versions.commonslang}" + integrationTestImplementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}" + integrationTestImplementation 'org.greenrobot:eventbus-java:3.3.1' + integrationTestImplementation('com.flipkart.zjsonpatch:zjsonpatch:0.4.16'){ + exclude(group:'com.fasterxml.jackson.core') + } + integrationTestImplementation 'org.slf4j:slf4j-api:2.0.12' + integrationTestImplementation 'com.selectivem.collections:special-collections-complete:1.4.0' + integrationTestImplementation "org.opensearch.plugin:lang-painless:${opensearch_version}" + integrationTestImplementation project(path:":opensearch-resource-sharing-spi", configuration: 'shadow') + } +} + //create source set 'integrationTest' //add classes from the main source set to the compilation and runtime classpaths of the integrationTest sourceSets { @@ -575,6 +640,7 @@ tasks.integrationTest.finalizedBy(jacocoTestReport) // report is always generate check.dependsOn integrationTest dependencies { + implementation project(path:":opensearch-resource-sharing-spi", configuration: 'shadow') implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}" implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}" implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}" @@ -730,35 +796,11 @@ dependencies { compileOnly "org.opensearch:opensearch:${opensearch_version}" - //integration test framework: - integrationTestImplementation('com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2') { - exclude(group: 'junit', module: 'junit') - } - integrationTestImplementation 'junit:junit:4.13.2' - integrationTestImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}" - integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}" - integrationTestImplementation 'commons-io:commons-io:2.18.0' - integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" - integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}" - integrationTestImplementation 'org.hamcrest:hamcrest:2.2' - integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}" - integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}" - integrationTestImplementation('org.awaitility:awaitility:4.3.0') { - exclude(group: 'org.hamcrest', module: 'hamcrest') - } - integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14' - integrationTestImplementation "org.opensearch.plugin:mapper-size:${opensearch_version}" - integrationTestImplementation "org.apache.httpcomponents:httpclient-cache:4.5.14" - integrationTestImplementation "org.apache.httpcomponents:httpclient:4.5.14" - integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14" - integrationTestImplementation "org.apache.httpcomponents:httpcore:4.4.16" - integrationTestImplementation "org.apache.httpcomponents:httpasyncclient:4.1.5" - integrationTestImplementation "org.mockito:mockito-core:5.16.1" - //spotless implementation('com.google.googlejavaformat:google-java-format:1.25.2') { exclude group: 'com.google.guava' } + } jar { diff --git a/scripts/build.sh b/scripts/build.sh index 4b2893f304..c4476731f5 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -77,6 +77,10 @@ echo "COPY ${distributions}/*.zip" mkdir -p $OUTPUT/plugins cp ${distributions}/*.zip ./$OUTPUT/plugins +# Publish jars +./gradlew :opensearch-resource-sharing-spi:publishToMavenLocal -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER +./gradlew publishAllPublicationsToStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER + ./gradlew publishPluginZipPublicationToZipStagingRepository -Dopensearch.version=$VERSION -Dbuild.snapshot=$SNAPSHOT -Dbuild.version_qualifier=$QUALIFIER mkdir -p $OUTPUT/maven/org/opensearch cp -r ./build/local-staging-repo/org/opensearch/. $OUTPUT/maven/org/opensearch diff --git a/settings.gradle b/settings.gradle index 1c3e7ff5aa..193587dee7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,3 +5,6 @@ */ rootProject.name = 'opensearch-security' + +include "spi" +project(":spi").name = "opensearch-resource-sharing-spi" diff --git a/spi/README.md b/spi/README.md new file mode 100644 index 0000000000..2d4d13f989 --- /dev/null +++ b/spi/README.md @@ -0,0 +1,167 @@ +# **Resource Sharing and Access Control SPI** + +This **Service Provider Interface (SPI)** provides the necessary **interfaces and mechanisms** to implement **Resource Sharing and Access Control** in OpenSearch. + +--- + +## **Usage** + +A plugin that **defines a resource** and aims to implement **access control** over that resource must **extend** the `ResourceSharingExtension` class to register itself as a **Resource Plugin**. + +### **Example: Implementing a Resource Plugin** +```java +public class SampleResourcePlugin extends Plugin implements SystemIndexPlugin, ResourceSharingExtension { + + // Override required methods + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + final SystemIndexDescriptor systemIndexDescriptor = + new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Sample index with resources"); + return Collections.singletonList(systemIndexDescriptor); + } + + @Override + public String getResourceType() { + return SampleResource.class.getCanonicalName(); + } + + @Override + public String getResourceIndex() { + return RESOURCE_INDEX_NAME; + } + + @Override + public ResourceParser getResourceParser() { + return new SampleResourceParser(); + } +} +``` + +--- + +## **Checklist for Implementing a Resource Plugin** + +To properly integrate with the **Resource Sharing and Access Control SPI**, follow these steps: + +### **1. Add Required Dependencies** +Include **`opensearch-security-client`** and **`opensearch-resource-sharing-spi`** in your **`build.gradle`** file. +Example: +```gradle +dependencies { + implementation 'org.opensearch:opensearch-security-client:VERSION' + implementation 'org.opensearch:opensearch-resource-sharing-spi:VERSION' +} +``` + +--- + +### **2. Register the Plugin Using the Java SPI Mechanism** +- Navigate to your plugin's `src/main/resources` folder. +- Locate or create the `META-INF/services` directory. +- Inside `META-INF/services`, create a file named: + ``` + org.opensearch.security.spi.resources.ResourceSharingExtension + ``` +- Edit the file and add a **single line** containing the **fully qualified class name** of your plugin implementation. + Example: + ``` + org.opensearch.sample.SampleResourcePlugin + ``` + > This step ensures that OpenSearch **dynamically loads your plugin** as a resource-sharing extension. + +--- + +### **3. Declare a Resource Class** +Each plugin must define a **resource class** that implements the `Resource` interface. +Example: +```java +public class SampleResource implements Resource { + private String id; + private String owner; + + // Constructor, getters, setters, etc. + + @Override + public String getResourceId() { + return id; + } +} +``` + +--- + +### **4. Implement a Resource Parser** +A **`ResourceParser`** is required to convert **resource data** from OpenSearch indices. +Example: +```java +public class SampleResourceParser implements ResourceParser { + @Override + public SampleResource parseXContent(XContentParser parser) throws IOException { + return SampleResource.fromXContent(parser); + } +} +``` + +--- + +### **5. Implement the `ResourceSharingExtension` Interface** +Ensure that your **plugin declaration class** implements `ResourceSharingExtension` and provides **all required methods**. + +**Important:** Mark the resource **index as a system index** to enforce security protections. + +--- + +### **6. Create a Client Accessor** +A **singleton accessor** should be created to manage the `ResourceSharingNodeClient`. +Example: +```java +public class ResourceSharingClientAccessor { + private static ResourceSharingNodeClient INSTANCE; + + private ResourceSharingClientAccessor() {} + + public static ResourceSharingNodeClient getResourceSharingClient(NodeClient nodeClient, Settings settings) { + if (INSTANCE == null) { + INSTANCE = new ResourceSharingNodeClient(nodeClient, settings); + } + return INSTANCE; + } +} +``` + +--- + +### **7. Utilize `ResourceSharingNodeClient` for Access Control** +Use the **client API methods** to manage resource sharing. + +#### **Example: Verifying Resource Access** +```java +Set scopes = Set.of("READ_ONLY"); +resourceSharingClient.verifyResourceAccess( + "resource-123", + "resource_index", + scopes, + ActionListener.wrap(isAuthorized -> { + if (isAuthorized) { + System.out.println("User has access to the resource."); + } else { + System.out.println("Access denied."); + } + }, e -> { + System.err.println("Failed to verify access: " + e.getMessage()); + }) +); +``` + +--- + +## **License** +This project is licensed under the **Apache 2.0 License**. + +--- + +## **Copyright** +© OpenSearch Contributors. + +--- diff --git a/spi/build.gradle b/spi/build.gradle new file mode 100644 index 0000000000..b8f33319b3 --- /dev/null +++ b/spi/build.gradle @@ -0,0 +1,86 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + id 'java' + id 'maven-publish' + id 'io.github.goooler.shadow' version "8.1.7" +} + +ext { + opensearch_version = System.getProperty("opensearch.version", "3.0.0-alpha1-SNAPSHOT") +} + +repositories { + mavenLocal() + mavenCentral() + maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" } +} + +dependencies { + compileOnly "org.opensearch:opensearch:${opensearch_version}" +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +task sourcesJar(type: Jar) { + archiveClassifier.set 'sources' + from sourceSets.main.allJava +} + +task javadocJar(type: Jar) { + archiveClassifier.set 'javadoc' + from tasks.javadoc +} + +publishing { + publications { + shadow(MavenPublication) { publication -> + project.shadow.component(publication) + artifact sourcesJar + artifact javadocJar + pom { + name.set("OpenSearch Resource Sharing SPI") + packaging = "jar" + description.set("OpenSearch Security Resource Sharing") + url.set("https://github.com/opensearch-project/security") + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + scm { + connection.set("scm:git@github.com:opensearch-project/security.git") + developerConnection.set("scm:git@github.com:opensearch-project/security.git") + url.set("https://github.com/opensearch-project/security.git") + } + developers { + developer { + name.set("OpenSearch Contributors") + url.set("https://github.com/opensearch-project") + } + } + } + } + } + repositories { + maven { + name = "Snapshots" + url = "https://aws.oss.sonatype.org/content/repositories/snapshots" + credentials { + username "$System.env.SONATYPE_USERNAME" + password "$System.env.SONATYPE_PASSWORD" + } + } + maven { + name = 'staging' + url = "${rootProject.buildDir}/local-staging-repo" + } + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java b/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java new file mode 100644 index 0000000000..72e0b7b5d1 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.xcontent.ToXContentFragment; + +/** + * Marker interface for all resources + * + * @opensearch.experimental + */ +public interface Resource extends NamedWriteable, ToXContentFragment { + /** + * Abstract method to get the resource name. + * Must be implemented by plugins defining resources. + * + * @return resource name + */ + String getResourceName(); +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java new file mode 100644 index 0000000000..c3b54a8c23 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources; + +import java.util.Arrays; + +/** + * This interface defines the two basic access scopes for resource-access. Plugins can decide whether to use these. + * Each plugin must implement their own scopes and manage them. + * These access scopes will then be used to verify the type of access being requested. + * + * @opensearch.experimental + */ +public interface ResourceAccessScope> { + String RESTRICTED = "restricted"; + String PUBLIC = "public"; + + static & ResourceAccessScope> E fromValue(Class enumClass, String value) { + for (E enumConstant : enumClass.getEnumConstants()) { + if (enumConstant.value().equalsIgnoreCase(value)) { + return enumConstant; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } + + String value(); + + static & ResourceAccessScope> String[] values(Class enumClass) { + return Arrays.stream(enumClass.getEnumConstants()).map(ResourceAccessScope::value).toArray(String[]::new); + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java new file mode 100644 index 0000000000..b02269322e --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources; + +import java.io.IOException; + +import org.opensearch.core.xcontent.XContentParser; + +/** + * Interface for parsing resources from XContentParser + * @param the type of resource to be parsed + * + * @opensearch.experimental + */ +public interface ResourceParser { + /** + * Parse source bytes supplied by the parser to a desired Resource type + * @param parser to parser bytes-ref json input + * @return the parsed object of Resource type + * @throws IOException if something went wrong while parsing + */ + T parseXContent(XContentParser parser) throws IOException; +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java new file mode 100644 index 0000000000..bbfc802d82 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources; + +/** + * This interface should be implemented by all the plugins that define one or more resources and need access control over those resources. + * + * @opensearch.experimental + */ +public interface ResourceSharingExtension { + + /** + * Type of the resource + * @return a string containing the type of the resource. A qualified class name can be supplied here. + */ + String getResourceType(); + + /** + * The index where resource is stored + * @return the name of the parent index where resource is stored + */ + String getResourceIndex(); + + /** + * The parser for the resource, which will be used by security plugin to parse the resource + * @return the parser for the resource + */ + ResourceParser getResourceParser(); +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java new file mode 100644 index 0000000000..560669112b --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.spi.resources.exceptions; + +import java.io.IOException; + +import org.opensearch.OpenSearchException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.rest.RestStatus; + +/** + * This class represents an exception that occurs during resource sharing operations. + * It extends the OpenSearchException class. + * + * @opensearch.experimental + */ +public class ResourceSharingException extends OpenSearchException { + public ResourceSharingException(Throwable cause) { + super(cause); + } + + public ResourceSharingException(String msg, Object... args) { + super(msg, args); + } + + public ResourceSharingException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } + + public ResourceSharingException(StreamInput in) throws IOException { + super(in); + } + + @Override + public RestStatus status() { + String message = getMessage(); + if (message.contains("not authorized")) { + return RestStatus.FORBIDDEN; + } else if (message.startsWith("No authenticated")) { + return RestStatus.UNAUTHORIZED; + } else if (message.contains("not found")) { + return RestStatus.NOT_FOUND; + } else if (message.contains("not a system index")) { + return RestStatus.BAD_REQUEST; + } else if (message.contains("is disabled")) { + return RestStatus.NOT_IMPLEMENTED; + } + + return RestStatus.INTERNAL_SERVER_ERROR; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/package-info.java b/spi/src/main/java/org/opensearch/security/spi/resources/package-info.java new file mode 100644 index 0000000000..f2e210a5e5 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/package-info.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * This package defines classes required to implement resource access control in OpenSearch. + * This package will be added as a dependency by all OpenSearch plugins that require resource access control. + * + * @opensearch.experimental + */ +package org.opensearch.security.spi.resources; diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/CreatedBy.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/CreatedBy.java new file mode 100644 index 0000000000..50bdd1aea7 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/CreatedBy.java @@ -0,0 +1,88 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources.sharing; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +/** + * This class is used to store information about the creator of a resource. + * + * @opensearch.experimental + */ +public class CreatedBy implements ToXContentFragment, NamedWriteable { + + private final Creator creatorType; + private final String creator; + + public CreatedBy(Creator creatorType, String creator) { + this.creatorType = creatorType; + this.creator = creator; + } + + public CreatedBy(StreamInput in) throws IOException { + this.creatorType = in.readEnum(Creator.class); + this.creator = in.readString(); + } + + public String getCreator() { + return creator; + } + + public Creator getCreatorType() { + return creatorType; + } + + @Override + public String toString() { + return "CreatedBy {" + this.creatorType.getName() + "='" + this.creator + '\'' + '}'; + } + + @Override + public String getWriteableName() { + return "created_by"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeEnum(Creator.valueOf(creatorType.name())); + out.writeString(creator); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject().field(creatorType.getName(), creator).endObject(); + } + + public static CreatedBy fromXContent(XContentParser parser) throws IOException { + String creator = null; + Creator creatorType = null; + XContentParser.Token token; + + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + creatorType = Creator.fromName(parser.currentName()); + } else if (token == XContentParser.Token.VALUE_STRING) { + creator = parser.text(); + } + } + + if (creator == null) { + throw new IllegalArgumentException(creatorType + " is required"); + } + + return new CreatedBy(creatorType, creator); + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Creator.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Creator.java new file mode 100644 index 0000000000..75e2415b93 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Creator.java @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources.sharing; + +/** + * This enum is used to store information about the creator of a resource. + * + * @opensearch.experimental + */ +public enum Creator { + USER("user"); + + private final String name; + + Creator(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Creator fromName(String name) { + for (Creator creator : values()) { + if (creator.name.equalsIgnoreCase(name)) { // Case-insensitive comparison + return creator; + } + } + throw new IllegalArgumentException("No enum constant for name: " + name); + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Recipient.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Recipient.java new file mode 100644 index 0000000000..77215071de --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Recipient.java @@ -0,0 +1,31 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources.sharing; + +/** + * Enum representing the recipients of a shared resource. + * It includes USERS, ROLES, and BACKEND_ROLES. + * + * @opensearch.experimental + */ +public enum Recipient { + USERS("users"), + ROLES("roles"), + BACKEND_ROLES("backend_roles"); + + private final String name; + + Recipient(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientType.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientType.java new file mode 100644 index 0000000000..d3b916abc2 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientType.java @@ -0,0 +1,24 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources.sharing; + +/** + * This class determines a type of recipient a resource can be shared with. + * An example type would be a user or a role. + * This class is used to determine the type of recipient a resource can be shared with. + * + * @opensearch.experimental + */ +public record RecipientType(String type) { + + @Override + public String toString() { + return type; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientTypeRegistry.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientTypeRegistry.java new file mode 100644 index 0000000000..a1bdb89089 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientTypeRegistry.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources.sharing; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class determines a collection of recipient types a resource can be shared with. + * Allows addition of other recipient types in the future. + * + * @opensearch.experimental + */ +public final class RecipientTypeRegistry { + // TODO: Check what size should this be. A cap should be added to avoid infinite addition of objects + private static final Integer REGISTRY_MAX_SIZE = 20; + private static final Map REGISTRY = new HashMap<>(10); + + public static void registerRecipientType(String key, RecipientType recipientType) { + if (REGISTRY.size() == REGISTRY_MAX_SIZE) { + throw new IllegalArgumentException("RecipientTypeRegistry is full. Cannot register more recipient types."); + } + REGISTRY.put(key, recipientType); + } + + public static RecipientType fromValue(String value) { + RecipientType type = REGISTRY.get(value); + if (type == null) { + throw new IllegalArgumentException("Unknown RecipientType: " + value + ". Must be 1 of these: " + REGISTRY.values()); + } + return type; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java new file mode 100644 index 0000000000..1690213872 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java @@ -0,0 +1,202 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources.sharing; + +import java.io.IOException; +import java.util.Objects; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +/** + * Represents a resource sharing configuration that manages access control for OpenSearch resources. + * This class holds information about shared resources including their source, creator, and sharing permissions. + * The class maintains information about: + *
    + *
  • The source index where the resource is defined
  • + *
  • The unique identifier of the resource
  • + *
  • The creator's information
  • + *
  • The sharing permissions and recipients
  • + *
+ * + * @opensearch.experimental + * @see org.opensearch.security.spi.resources.sharing.CreatedBy + * @see org.opensearch.security.spi.resources.sharing.ShareWith + */ +public class ResourceSharing implements ToXContentFragment, NamedWriteable { + + /** + * The index where the resource is defined + */ + private String sourceIdx; + + /** + * The unique identifier of the resource + */ + private String resourceId; + + /** + * Information about who created the resource + */ + private CreatedBy createdBy; + + /** + * Information about with whom the resource is shared with + */ + private ShareWith shareWith; + + public ResourceSharing(String sourceIdx, String resourceId, CreatedBy createdBy, ShareWith shareWith) { + this.sourceIdx = sourceIdx; + this.resourceId = resourceId; + this.createdBy = createdBy; + this.shareWith = shareWith; + } + + public String getSourceIdx() { + return sourceIdx; + } + + public void setSourceIdx(String sourceIdx) { + this.sourceIdx = sourceIdx; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public CreatedBy getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(CreatedBy createdBy) { + this.createdBy = createdBy; + } + + public ShareWith getShareWith() { + return shareWith; + } + + public void setShareWith(ShareWith shareWith) { + this.shareWith = shareWith; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ResourceSharing resourceSharing = (ResourceSharing) o; + return Objects.equals(getSourceIdx(), resourceSharing.getSourceIdx()) + && Objects.equals(getResourceId(), resourceSharing.getResourceId()) + && Objects.equals(getCreatedBy(), resourceSharing.getCreatedBy()) + && Objects.equals(getShareWith(), resourceSharing.getShareWith()); + } + + @Override + public int hashCode() { + return Objects.hash(getSourceIdx(), getResourceId(), getCreatedBy(), getShareWith()); + } + + @Override + public String toString() { + return "Resource {" + + "sourceIdx='" + + sourceIdx + + '\'' + + ", resourceId='" + + resourceId + + '\'' + + ", createdBy=" + + createdBy + + ", sharedWith=" + + shareWith + + '}'; + } + + @Override + public String getWriteableName() { + return "resource_sharing"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(sourceIdx); + out.writeString(resourceId); + createdBy.writeTo(out); + if (shareWith != null) { + out.writeBoolean(true); + shareWith.writeTo(out); + } else { + out.writeBoolean(false); + } + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject().field("source_idx", sourceIdx).field("resource_id", resourceId).field("created_by"); + createdBy.toXContent(builder, params); + if (shareWith != null && !shareWith.getSharedWithScopes().isEmpty()) { + builder.field("share_with"); + shareWith.toXContent(builder, params); + } + return builder.endObject(); + } + + public static ResourceSharing fromXContent(XContentParser parser) throws IOException { + String sourceIdx = null; + String resourceId = null; + CreatedBy createdBy = null; + ShareWith shareWith = null; + + String currentFieldName = null; + XContentParser.Token token; + + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else { + switch (Objects.requireNonNull(currentFieldName)) { + case "source_idx": + sourceIdx = parser.text(); + break; + case "resource_id": + resourceId = parser.text(); + break; + case "created_by": + createdBy = CreatedBy.fromXContent(parser); + break; + case "share_with": + shareWith = ShareWith.fromXContent(parser); + break; + default: + parser.skipChildren(); + break; + } + } + } + + validateRequiredField("source_idx", sourceIdx); + validateRequiredField("resource_id", resourceId); + validateRequiredField("created_by", createdBy); + + return new ResourceSharing(sourceIdx, resourceId, createdBy, shareWith); + } + + private static void validateRequiredField(String field, T value) { + if (value == null) { + throw new IllegalArgumentException(field + " is required"); + } + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ShareWith.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ShareWith.java new file mode 100644 index 0000000000..267bb7ece0 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ShareWith.java @@ -0,0 +1,103 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources.sharing; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +/** + * This class contains information about whom a resource is shared with and at what scope. + * Example: + * "share_with": { + * "read_only": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * }, + * "read_write": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * } + * } + * + * @opensearch.experimental + */ +public class ShareWith implements ToXContentFragment, NamedWriteable { + + /** + * A set of objects representing the scopes and their associated users, roles, and backend roles. + */ + private final Set sharedWithScopes; + + public ShareWith(Set sharedWithScopes) { + this.sharedWithScopes = sharedWithScopes; + } + + public ShareWith(StreamInput in) throws IOException { + this.sharedWithScopes = in.readSet(SharedWithScope::new); + } + + public Set getSharedWithScopes() { + return sharedWithScopes; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + + for (SharedWithScope scope : sharedWithScopes) { + scope.toXContent(builder, params); + } + + return builder.endObject(); + } + + public static ShareWith fromXContent(XContentParser parser) throws IOException { + Set sharedWithScopes = new HashSet<>(); + + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + parser.nextToken(); + } + + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + // Each field in the object represents a SharedWithScope + if (token == XContentParser.Token.FIELD_NAME) { + SharedWithScope scope = SharedWithScope.fromXContent(parser); + sharedWithScopes.add(scope); + } + } + + return new ShareWith(sharedWithScopes); + } + + @Override + public String getWriteableName() { + return "share_with"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeCollection(sharedWithScopes); + } + + @Override + public String toString() { + return "ShareWith " + sharedWithScopes; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithScope.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithScope.java new file mode 100644 index 0000000000..1dfca103a3 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithScope.java @@ -0,0 +1,169 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources.sharing; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.opensearch.core.common.io.stream.NamedWriteable; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentFragment; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +/** + * This class represents the scope at which a resource is shared with for a particular scope. + * Example: + * "read_only": { + * "users": [], + * "roles": [], + * "backend_roles": [] + * } + * where "users", "roles" and "backend_roles" are the recipient entities + * + * @opensearch.experimental + */ +public class SharedWithScope implements ToXContentFragment, NamedWriteable { + + private final String scope; + + private final ScopeRecipients scopeRecipients; + + public SharedWithScope(String scope, ScopeRecipients scopeRecipients) { + this.scope = scope; + this.scopeRecipients = scopeRecipients; + } + + public SharedWithScope(StreamInput in) throws IOException { + this.scope = in.readString(); + this.scopeRecipients = new ScopeRecipients(in); + } + + public String getScope() { + return scope; + } + + public ScopeRecipients getSharedWithPerScope() { + return scopeRecipients; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.field(scope); + builder.startObject(); + + scopeRecipients.toXContent(builder, params); + + return builder.endObject(); + } + + public static SharedWithScope fromXContent(XContentParser parser) throws IOException { + String scope = parser.currentName(); + + parser.nextToken(); + + ScopeRecipients scopeRecipients = ScopeRecipients.fromXContent(parser); + + return new SharedWithScope(scope, scopeRecipients); + } + + @Override + public String toString() { + return "{" + scope + ": " + scopeRecipients + '}'; + } + + @Override + public String getWriteableName() { + return "shared_with_scope"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(scope); + out.writeNamedWriteable(scopeRecipients); + } + + /** + * This class represents the entities with whom a resource is shared with for a given scope. + * + * @opensearch.experimental + */ + public static class ScopeRecipients implements ToXContentFragment, NamedWriteable { + + private final Map> recipients; + + public ScopeRecipients(Map> recipients) { + if (recipients == null) { + throw new IllegalArgumentException("Recipients map cannot be null"); + } + this.recipients = recipients; + } + + public ScopeRecipients(StreamInput in) throws IOException { + this.recipients = in.readMap( + key -> RecipientTypeRegistry.fromValue(key.readString()), + input -> input.readSet(StreamInput::readString) + ); + } + + public Map> getRecipients() { + return recipients; + } + + @Override + public String getWriteableName() { + return "scope_recipients"; + } + + public static ScopeRecipients fromXContent(XContentParser parser) throws IOException { + Map> recipients = new HashMap<>(); + + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + String fieldName = parser.currentName(); + RecipientType recipientType = RecipientTypeRegistry.fromValue(fieldName); + + parser.nextToken(); + Set values = new HashSet<>(); + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + values.add(parser.text()); + } + recipients.put(recipientType, values); + } + } + + return new ScopeRecipients(recipients); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap( + recipients, + (streamOutput, recipientType) -> streamOutput.writeString(recipientType.type()), + (streamOutput, strings) -> streamOutput.writeCollection(strings, StreamOutput::writeString) + ); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + if (recipients.isEmpty()) { + return builder; + } + for (Map.Entry> entry : recipients.entrySet()) { + builder.array(entry.getKey().type(), entry.getValue().toArray()); + } + return builder; + } + } +} diff --git a/spi/src/test/java/org/opensearch/security/spi/resources/CreatedByTests.java b/spi/src/test/java/org/opensearch/security/spi/resources/CreatedByTests.java new file mode 100644 index 0000000000..7d6eb5c61a --- /dev/null +++ b/spi/src/test/java/org/opensearch/security/spi/resources/CreatedByTests.java @@ -0,0 +1,320 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources; + +import java.io.IOException; + +import org.hamcrest.MatcherAssert; +import org.junit.Test; + +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.security.spi.resources.sharing.CreatedBy; +import org.opensearch.security.spi.resources.sharing.Creator; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test class for CreatedBy class + * + * @opensearch.experimental + */ +public class CreatedByTests { + + private static final Creator CREATOR_TYPE = Creator.USER; + + @Test + public void testCreatedByConstructorWithValidUser() { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, expectedUser); + + MatcherAssert.assertThat(expectedUser, is(equalTo(createdBy.getCreator()))); + } + + @Test + public void testCreatedByFromStreamInput() throws IOException { + String expectedUser = "testUser"; + + try (BytesStreamOutput out = new BytesStreamOutput()) { + out.writeEnum(Creator.valueOf(CREATOR_TYPE.name())); + out.writeString(expectedUser); + + StreamInput in = out.bytes().streamInput(); + + CreatedBy createdBy = new CreatedBy(in); + + MatcherAssert.assertThat(expectedUser, is(equalTo(createdBy.getCreator()))); + } + } + + @Test + public void testCreatedByWithEmptyStreamInput() throws IOException { + + try (StreamInput mockStreamInput = mock(StreamInput.class)) { + when(mockStreamInput.readString()).thenThrow(new IOException("EOF")); + + assertThrows(IOException.class, () -> new CreatedBy(mockStreamInput)); + } + } + + @Test + public void testCreatedByWithEmptyUser() { + + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, ""); + MatcherAssert.assertThat("", equalTo(createdBy.getCreator())); + } + + @Test + public void testCreatedByWithIOException() throws IOException { + + try (StreamInput mockStreamInput = mock(StreamInput.class)) { + when(mockStreamInput.readString()).thenThrow(new IOException("Test IOException")); + + assertThrows(IOException.class, () -> new CreatedBy(mockStreamInput)); + } + } + + @Test + public void testCreatedByWithLongUsername() { + String longUsername = "a".repeat(10000); + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, longUsername); + MatcherAssert.assertThat(longUsername, equalTo(createdBy.getCreator())); + } + + @Test + public void testCreatedByWithUnicodeCharacters() { + String unicodeUsername = "用户こんにちは"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, unicodeUsername); + MatcherAssert.assertThat(unicodeUsername, equalTo(createdBy.getCreator())); + } + + @Test + public void testFromXContentThrowsExceptionWhenUserFieldIsMissing() throws IOException { + String emptyJson = "{}"; + IllegalArgumentException exception; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, emptyJson)) { + + exception = assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + + MatcherAssert.assertThat("null is required", equalTo(exception.getMessage())); + } + + @Test + public void testFromXContentWithEmptyInput() throws IOException { + String emptyJson = "{}"; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, emptyJson)) { + + assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + } + + @Test + public void testFromXContentWithExtraFields() throws IOException { + String jsonWithExtraFields = "{\"user\": \"testUser\", \"extraField\": \"value\"}"; + XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, jsonWithExtraFields); + + assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + + @Test + public void testFromXContentWithIncorrectFieldType() throws IOException { + String jsonWithIncorrectType = "{\"user\": 12345}"; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, jsonWithIncorrectType)) { + + assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + } + + @Test + public void testFromXContentWithEmptyUser() throws IOException { + String emptyJson = "{\"" + CREATOR_TYPE + "\": \"\" }"; + CreatedBy createdBy; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, emptyJson)) { + parser.nextToken(); + + createdBy = CreatedBy.fromXContent(parser); + } + + MatcherAssert.assertThat(CREATOR_TYPE, equalTo(createdBy.getCreatorType())); + MatcherAssert.assertThat("", equalTo(createdBy.getCreator())); + } + + @Test + public void testFromXContentWithNullUserValue() throws IOException { + String jsonWithNullUser = "{\"user\": null}"; + try (XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, jsonWithNullUser)) { + + assertThrows(IllegalArgumentException.class, () -> CreatedBy.fromXContent(parser)); + } + } + + @Test + public void testFromXContentWithValidUser() throws IOException { + String json = "{\"user\":\"testUser\"}"; + XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, json); + + CreatedBy createdBy = CreatedBy.fromXContent(parser); + + MatcherAssert.assertThat(createdBy, notNullValue()); + MatcherAssert.assertThat("testUser", equalTo(createdBy.getCreator())); + } + + @Test + public void testGetCreatorReturnsCorrectValue() { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, expectedUser); + + String actualUser = createdBy.getCreator(); + + MatcherAssert.assertThat(expectedUser, equalTo(actualUser)); + } + + @Test + public void testGetCreatorWithNullString() { + + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, null); + MatcherAssert.assertThat(createdBy.getCreator(), nullValue()); + } + + @Test + public void testGetWriteableNameReturnsCorrectString() { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, "testUser"); + MatcherAssert.assertThat("created_by", equalTo(createdBy.getWriteableName())); + } + + @Test + public void testToStringWithEmptyUser() { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, ""); + String result = createdBy.toString(); + MatcherAssert.assertThat("CreatedBy {user=''}", equalTo(result)); + } + + @Test + public void testToStringWithNullUser() { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, (String) null); + String result = createdBy.toString(); + MatcherAssert.assertThat("CreatedBy {user='null'}", equalTo(result)); + } + + @Test + public void testToStringWithLongUserName() { + + String longUserName = "a".repeat(1000); + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, longUserName); + String result = createdBy.toString(); + MatcherAssert.assertThat(result.startsWith("CreatedBy {user='"), is(true)); + MatcherAssert.assertThat(result.endsWith("'}"), is(true)); + MatcherAssert.assertThat(1019, equalTo(result.length())); + } + + @Test + public void testToXContentWithEmptyUser() throws IOException { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, ""); + XContentBuilder builder = JsonXContent.contentBuilder(); + + createdBy.toXContent(builder, null); + String result = builder.toString(); + MatcherAssert.assertThat("{\"user\":\"\"}", equalTo(result)); + } + + @Test + public void testWriteToWithExceptionInStreamOutput() throws IOException { + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, "user1"); + try (StreamOutput failingOutput = new StreamOutput() { + @Override + public void writeByte(byte b) throws IOException { + throw new IOException("Simulated IO exception"); + } + + @Override + public void writeBytes(byte[] b, int offset, int length) throws IOException { + throw new IOException("Simulated IO exception"); + } + + @Override + public void flush() throws IOException { + + } + + @Override + public void close() throws IOException { + + } + + @Override + public void reset() throws IOException { + + } + }) { + + assertThrows(IOException.class, () -> createdBy.writeTo(failingOutput)); + } + } + + @Test + public void testWriteToWithLongUserName() throws IOException { + String longUserName = "a".repeat(65536); + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, longUserName); + BytesStreamOutput out = new BytesStreamOutput(); + createdBy.writeTo(out); + MatcherAssert.assertThat(out.size(), greaterThan(65536)); + } + + @Test + public void test_createdByToStringReturnsCorrectFormat() { + String testUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, testUser); + + String expected = "CreatedBy {user='" + testUser + "'}"; + String actual = createdBy.toString(); + + MatcherAssert.assertThat(expected, equalTo(actual)); + } + + @Test + public void test_toXContent_serializesCorrectly() throws IOException { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, expectedUser); + XContentBuilder builder = XContentFactory.jsonBuilder(); + + createdBy.toXContent(builder, null); + + String expectedJson = "{\"user\":\"testUser\"}"; + MatcherAssert.assertThat(expectedJson, equalTo(builder.toString())); + } + + @Test + public void test_writeTo_writesUserCorrectly() throws IOException { + String expectedUser = "testUser"; + CreatedBy createdBy = new CreatedBy(CREATOR_TYPE, expectedUser); + + BytesStreamOutput out = new BytesStreamOutput(); + createdBy.writeTo(out); + + StreamInput in = out.bytes().streamInput(); + in.readString(); + String actualUser = in.readString(); + + MatcherAssert.assertThat(expectedUser, equalTo(actualUser)); + } + +} diff --git a/spi/src/test/java/org/opensearch/security/spi/resources/RecipientTypeRegistryTests.java b/spi/src/test/java/org/opensearch/security/spi/resources/RecipientTypeRegistryTests.java new file mode 100644 index 0000000000..8b0bfa3297 --- /dev/null +++ b/spi/src/test/java/org/opensearch/security/spi/resources/RecipientTypeRegistryTests.java @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources; + +import org.hamcrest.MatcherAssert; +import org.junit.Test; + +import org.opensearch.security.spi.resources.sharing.RecipientType; +import org.opensearch.security.spi.resources.sharing.RecipientTypeRegistry; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThrows; + +/** + * Tests for {@link RecipientTypeRegistry}. + * + * @opensearch.experimental + */ +public class RecipientTypeRegistryTests { + + @Test + public void testFromValue() { + RecipientTypeRegistry.registerRecipientType("ble1", new RecipientType("ble1")); + RecipientTypeRegistry.registerRecipientType("ble2", new RecipientType("ble2")); + + // Valid Value + RecipientType type = RecipientTypeRegistry.fromValue("ble1"); + MatcherAssert.assertThat(type, notNullValue()); + MatcherAssert.assertThat(type.type(), is(equalTo("ble1"))); + + // Invalid Value + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> RecipientTypeRegistry.fromValue("bleble")); + MatcherAssert.assertThat("Unknown RecipientType: bleble. Must be 1 of these: [ble1, ble2]", is(equalTo(exception.getMessage()))); + } +} diff --git a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java new file mode 100644 index 0000000000..d7ffa0ce5e --- /dev/null +++ b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java @@ -0,0 +1,284 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hamcrest.MatcherAssert; +import org.junit.Before; +import org.junit.Test; + +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.security.spi.resources.sharing.RecipientType; +import org.opensearch.security.spi.resources.sharing.RecipientTypeRegistry; +import org.opensearch.security.spi.resources.sharing.ShareWith; +import org.opensearch.security.spi.resources.sharing.SharedWithScope; + +import org.mockito.Mockito; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Test class for ShareWith class + * + * @opensearch.experimental + */ +public class ShareWithTests { + + @Before + public void setupResourceRecipientTypes() { + initializeRecipientTypes(); + } + + @Test + public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOException { + String json = "{\"read_only\": {\"users\": [\"user1\"], \"roles\": [], \"backend_roles\": []}}"; + XContentParser parser = JsonXContent.jsonXContent.createParser(null, null, json); + + parser.nextToken(); + + ShareWith shareWith = ShareWith.fromXContent(parser); + + MatcherAssert.assertThat(shareWith, notNullValue()); + Set sharedWithScopes = shareWith.getSharedWithScopes(); + MatcherAssert.assertThat(sharedWithScopes, notNullValue()); + MatcherAssert.assertThat(1, equalTo(sharedWithScopes.size())); + + SharedWithScope scope = sharedWithScopes.iterator().next(); + MatcherAssert.assertThat("read_only", equalTo(scope.getScope())); + + SharedWithScope.ScopeRecipients scopeRecipients = scope.getSharedWithPerScope(); + MatcherAssert.assertThat(scopeRecipients, notNullValue()); + Map> recipients = scopeRecipients.getRecipients(); + MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), is(1)); + MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())), contains("user1")); + MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), is(0)); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), + is(0) + ); + } + + @Test + public void testFromXContentWithEmptyInput() throws IOException { + String emptyJson = "{}"; + XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, null, emptyJson); + + ShareWith result = ShareWith.fromXContent(parser); + + MatcherAssert.assertThat(result, notNullValue()); + MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty())); + } + + @Test + public void testFromXContentWithStartObject() throws IOException { + XContentParser parser; + try (XContentBuilder builder = XContentFactory.jsonBuilder()) { + builder.startObject() + .startObject(ResourceAccessScope.RESTRICTED) + .array("users", "user1", "user2") + .array("roles", "role1") + .array("backend_roles", "backend_role1") + .endObject() + .startObject(ResourceAccessScope.PUBLIC) + .array("users", "user3") + .array("roles", "role2", "role3") + .array("backend_roles") + .endObject() + .endObject(); + + parser = JsonXContent.jsonXContent.createParser(null, null, builder.toString()); + } + + parser.nextToken(); + + ShareWith shareWith = ShareWith.fromXContent(parser); + + MatcherAssert.assertThat(shareWith, notNullValue()); + Set scopes = shareWith.getSharedWithScopes(); + MatcherAssert.assertThat(scopes.size(), equalTo(2)); + + for (SharedWithScope scope : scopes) { + SharedWithScope.ScopeRecipients perScope = scope.getSharedWithPerScope(); + Map> recipients = perScope.getRecipients(); + if (scope.getScope().equals(ResourceAccessScope.RESTRICTED)) { + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), + is(2) + ); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), + is(1) + ); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), + is(1) + ); + } else if (scope.getScope().equals(ResourceAccessScope.PUBLIC)) { + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), + is(1) + ); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), + is(2) + ); + MatcherAssert.assertThat( + recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), + is(0) + ); + } + } + } + + @Test + public void testFromXContentWithUnexpectedEndOfInput() throws IOException { + XContentParser mockParser = mock(XContentParser.class); + when(mockParser.currentToken()).thenReturn(XContentParser.Token.START_OBJECT); + when(mockParser.nextToken()).thenReturn(XContentParser.Token.END_OBJECT, (XContentParser.Token) null); + + ShareWith result = ShareWith.fromXContent(mockParser); + + MatcherAssert.assertThat(result, notNullValue()); + MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty())); + } + + @Test + public void testToXContentBuildsCorrectly() throws IOException { + SharedWithScope scope = new SharedWithScope( + "scope1", + new SharedWithScope.ScopeRecipients(Map.of(new RecipientType("users"), Set.of("bleh"))) + ); + + Set scopes = new HashSet<>(); + scopes.add(scope); + + ShareWith shareWith = new ShareWith(scopes); + + XContentBuilder builder = JsonXContent.contentBuilder(); + + shareWith.toXContent(builder, ToXContent.EMPTY_PARAMS); + + String result = builder.toString(); + + String expected = "{\"scope1\":{\"users\":[\"bleh\"]}}"; + + MatcherAssert.assertThat(expected.length(), equalTo(result.length())); + MatcherAssert.assertThat(expected, equalTo(result)); + } + + @Test + public void testWriteToWithEmptySet() throws IOException { + Set emptySet = Collections.emptySet(); + ShareWith shareWith = new ShareWith(emptySet); + StreamOutput mockOutput = Mockito.mock(StreamOutput.class); + + shareWith.writeTo(mockOutput); + + verify(mockOutput).writeCollection(emptySet); + } + + @Test + public void testWriteToWithIOException() throws IOException { + Set set = new HashSet<>(); + set.add(new SharedWithScope("test", new SharedWithScope.ScopeRecipients(Map.of()))); + ShareWith shareWith = new ShareWith(set); + StreamOutput mockOutput = Mockito.mock(StreamOutput.class); + + doThrow(new IOException("Simulated IO exception")).when(mockOutput).writeCollection(set); + + assertThrows(IOException.class, () -> shareWith.writeTo(mockOutput)); + } + + @Test + public void testWriteToWithLargeSet() throws IOException { + Set largeSet = new HashSet<>(); + for (int i = 0; i < 10000; i++) { + largeSet.add(new SharedWithScope("scope" + i, new SharedWithScope.ScopeRecipients(Map.of()))); + } + ShareWith shareWith = new ShareWith(largeSet); + StreamOutput mockOutput = Mockito.mock(StreamOutput.class); + + shareWith.writeTo(mockOutput); + + verify(mockOutput).writeCollection(largeSet); + } + + @Test + public void test_fromXContent_emptyObject() throws IOException { + XContentParser parser; + try (XContentBuilder builder = XContentFactory.jsonBuilder()) { + builder.startObject().endObject(); + parser = XContentType.JSON.xContent().createParser(null, null, builder.toString()); + } + + ShareWith shareWith = ShareWith.fromXContent(parser); + + MatcherAssert.assertThat(shareWith.getSharedWithScopes(), is(empty())); + } + + @Test + public void test_writeSharedWithScopesToStream() throws IOException { + StreamOutput mockStreamOutput = Mockito.mock(StreamOutput.class); + + Set sharedWithScopes = new HashSet<>(); + sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.RESTRICTED, new SharedWithScope.ScopeRecipients(Map.of()))); + sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.PUBLIC, new SharedWithScope.ScopeRecipients(Map.of()))); + + ShareWith shareWith = new ShareWith(sharedWithScopes); + + shareWith.writeTo(mockStreamOutput); + + verify(mockStreamOutput, times(1)).writeCollection(eq(sharedWithScopes)); + } + + private void initializeRecipientTypes() { + RecipientTypeRegistry.registerRecipientType("users", new RecipientType("users")); + RecipientTypeRegistry.registerRecipientType("roles", new RecipientType("roles")); + RecipientTypeRegistry.registerRecipientType("backend_roles", new RecipientType("backend_roles")); + } +} + +enum DefaultRecipientType { + USERS("users"), + ROLES("roles"), + BACKEND_ROLES("backend_roles"); + + private final String name; + + DefaultRecipientType(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 0802cb856c..843553d971 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -116,6 +116,7 @@ import org.opensearch.indices.IndicesService; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.plugins.ClusterPlugin; +import org.opensearch.plugins.ExtensiblePlugin; import org.opensearch.plugins.ExtensionAwarePlugin; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.plugins.MapperPlugin; @@ -236,9 +237,10 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin, + IdentityPlugin, // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings ExtensionAwarePlugin, - IdentityPlugin + ExtensiblePlugin // CS-ENFORCE-SINGLE { @@ -1194,7 +1196,7 @@ public Collection createComponents( // NOTE: We need to create DefaultInterClusterRequestEvaluator before creating ConfigurationRepository since the latter requires // security index to be accessible which means - // communciation with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence + // communication with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence // the base values from opensearch.yml // is used to first establish trust between same cluster nodes and there after dynamic config is loaded if enabled. if (DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS.equals(className)) { @@ -2141,8 +2143,8 @@ public Collection getSystemIndexDescriptors(Settings sett ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX ); - final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); - return Collections.singletonList(systemIndexDescriptor); + final SystemIndexDescriptor securityIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); + return List.of(securityIndexDescriptor); } @Override @@ -2201,6 +2203,13 @@ private void tryAddSecurityProvider() { }); } + // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings + @Override + public void loadExtensions(ExtensiblePlugin.ExtensionLoader loader) { + // Resource Sharing extensions will be loaded here + } + // CS-ENFORCE-SINGLE + public static class GuiceHolder implements LifecycleComponent { private static RepositoriesService repositoriesService; diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 307db9cbcd..633b85cff6 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -43,6 +43,7 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_CONFIG_PREFIX = "_opendistro_security_"; + public static final String SECURITY_SETTINGS_PREFIX = "plugins.security."; public static final String OPENDISTRO_SECURITY_CHANNEL_TYPE = OPENDISTRO_SECURITY_CONFIG_PREFIX + "channel_type"; @@ -131,11 +132,11 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX = ".opendistro_security"; - public static final String SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE = "plugins.security.enable_snapshot_restore_privilege"; + public static final String SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE = SECURITY_SETTINGS_PREFIX + "enable_snapshot_restore_privilege"; public static final boolean SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE = true; - public static final String SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES = - "plugins.security.check_snapshot_restore_write_privileges"; + public static final String SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES = SECURITY_SETTINGS_PREFIX + + "check_snapshot_restore_write_privileges"; public static final boolean SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES = true; public static final Set SECURITY_SNAPSHOT_RESTORE_NEEDED_WRITE_PRIVILEGES = Collections.unmodifiableSet( new HashSet(Arrays.asList("indices:admin/create", "indices:data/write/index" @@ -143,37 +144,39 @@ public class ConfigConstants { )) ); - public static final String SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = "plugins.security.cert.intercluster_request_evaluator_class"; + public static final String SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = SECURITY_SETTINGS_PREFIX + + "cert.intercluster_request_evaluator_class"; public static final String OPENDISTRO_SECURITY_ACTION_NAME = OPENDISTRO_SECURITY_CONFIG_PREFIX + "action_name"; - public static final String SECURITY_AUTHCZ_ADMIN_DN = "plugins.security.authcz.admin_dn"; - public static final String SECURITY_CONFIG_INDEX_NAME = "plugins.security.config_index_name"; - public static final String SECURITY_AUTHCZ_IMPERSONATION_DN = "plugins.security.authcz.impersonation_dn"; - public static final String SECURITY_AUTHCZ_REST_IMPERSONATION_USERS = "plugins.security.authcz.rest_impersonation_user"; + public static final String SECURITY_AUTHCZ_ADMIN_DN = SECURITY_SETTINGS_PREFIX + "authcz.admin_dn"; + public static final String SECURITY_CONFIG_INDEX_NAME = SECURITY_SETTINGS_PREFIX + "config_index_name"; + public static final String SECURITY_AUTHCZ_IMPERSONATION_DN = SECURITY_SETTINGS_PREFIX + "authcz.impersonation_dn"; + public static final String SECURITY_AUTHCZ_REST_IMPERSONATION_USERS = SECURITY_SETTINGS_PREFIX + "authcz.rest_impersonation_user"; public static final String BCRYPT = "bcrypt"; public static final String PBKDF2 = "pbkdf2"; - public static final String SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS = "plugins.security.password.hashing.bcrypt.rounds"; + public static final String SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS = SECURITY_SETTINGS_PREFIX + "password.hashing.bcrypt.rounds"; public static final int SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS_DEFAULT = 12; - public static final String SECURITY_PASSWORD_HASHING_BCRYPT_MINOR = "plugins.security.password.hashing.bcrypt.minor"; + public static final String SECURITY_PASSWORD_HASHING_BCRYPT_MINOR = SECURITY_SETTINGS_PREFIX + "password.hashing.bcrypt.minor"; public static final String SECURITY_PASSWORD_HASHING_BCRYPT_MINOR_DEFAULT = "Y"; - public static final String SECURITY_PASSWORD_HASHING_ALGORITHM = "plugins.security.password.hashing.algorithm"; + public static final String SECURITY_PASSWORD_HASHING_ALGORITHM = SECURITY_SETTINGS_PREFIX + "password.hashing.algorithm"; public static final String SECURITY_PASSWORD_HASHING_ALGORITHM_DEFAULT = BCRYPT; - public static final String SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS = "plugins.security.password.hashing.pbkdf2.iterations"; + public static final String SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS = SECURITY_SETTINGS_PREFIX + + "password.hashing.pbkdf2.iterations"; public static final int SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS_DEFAULT = 600_000; - public static final String SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH = "plugins.security.password.hashing.pbkdf2.length"; + public static final String SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH = SECURITY_SETTINGS_PREFIX + "password.hashing.pbkdf2.length"; public static final int SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH_DEFAULT = 256; - public static final String SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION = "plugins.security.password.hashing.pbkdf2.function"; + public static final String SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION = SECURITY_SETTINGS_PREFIX + "password.hashing.pbkdf2.function"; public static final String SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION_DEFAULT = Hmac.SHA256.name(); - public static final String SECURITY_AUDIT_TYPE_DEFAULT = "plugins.security.audit.type"; - public static final String SECURITY_AUDIT_CONFIG_DEFAULT = "plugins.security.audit.config"; - public static final String SECURITY_AUDIT_CONFIG_ROUTES = "plugins.security.audit.routes"; - public static final String SECURITY_AUDIT_CONFIG_ENDPOINTS = "plugins.security.audit.endpoints"; - public static final String SECURITY_AUDIT_THREADPOOL_SIZE = "plugins.security.audit.threadpool.size"; - public static final String SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN = "plugins.security.audit.threadpool.max_queue_len"; + public static final String SECURITY_AUDIT_TYPE_DEFAULT = SECURITY_SETTINGS_PREFIX + "audit.type"; + public static final String SECURITY_AUDIT_CONFIG_DEFAULT = SECURITY_SETTINGS_PREFIX + "audit.config"; + public static final String SECURITY_AUDIT_CONFIG_ROUTES = SECURITY_SETTINGS_PREFIX + "audit.routes"; + public static final String SECURITY_AUDIT_CONFIG_ENDPOINTS = SECURITY_SETTINGS_PREFIX + "audit.endpoints"; + public static final String SECURITY_AUDIT_THREADPOOL_SIZE = SECURITY_SETTINGS_PREFIX + "audit.threadpool.size"; + public static final String SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN = SECURITY_SETTINGS_PREFIX + "audit.threadpool.max_queue_len"; public static final String OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY = "opendistro_security.audit.log_request_body"; public static final String OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES = "opendistro_security.audit.resolve_indices"; public static final String OPENDISTRO_SECURITY_AUDIT_ENABLE_REST = "opendistro_security.audit.enable_rest"; @@ -188,13 +191,13 @@ public class ConfigConstants { ); public static final String OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS = "opendistro_security.audit.ignore_users"; public static final String OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS = "opendistro_security.audit.ignore_requests"; - public static final String SECURITY_AUDIT_IGNORE_HEADERS = "plugins.security.audit.ignore_headers"; + public static final String SECURITY_AUDIT_IGNORE_HEADERS = SECURITY_SETTINGS_PREFIX + "audit.ignore_headers"; public static final String OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS = "opendistro_security.audit.resolve_bulk_requests"; public static final boolean OPENDISTRO_SECURITY_AUDIT_SSL_VERIFY_HOSTNAMES_DEFAULT = true; public static final boolean OPENDISTRO_SECURITY_AUDIT_SSL_ENABLE_SSL_CLIENT_AUTH_DEFAULT = false; public static final String OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS = "opendistro_security.audit.exclude_sensitive_headers"; - public static final String SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX = "plugins.security.audit.config."; + public static final String SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX = SECURITY_SETTINGS_PREFIX + "audit.config."; // Internal Opensearch data_stream public static final String SECURITY_AUDIT_OPENSEARCH_DATASTREAM_NAME = "data_stream.name"; @@ -237,31 +240,31 @@ public class ConfigConstants { public static final String SECURITY_AUDIT_LOG4J_LEVEL = "log4j.level"; // retry - public static final String SECURITY_AUDIT_RETRY_COUNT = "plugins.security.audit.config.retry_count"; - public static final String SECURITY_AUDIT_RETRY_DELAY_MS = "plugins.security.audit.config.retry_delay_ms"; + public static final String SECURITY_AUDIT_RETRY_COUNT = SECURITY_SETTINGS_PREFIX + "audit.config.retry_count"; + public static final String SECURITY_AUDIT_RETRY_DELAY_MS = SECURITY_SETTINGS_PREFIX + "audit.config.retry_delay_ms"; - public static final String SECURITY_KERBEROS_KRB5_FILEPATH = "plugins.security.kerberos.krb5_filepath"; - public static final String SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH = "plugins.security.kerberos.acceptor_keytab_filepath"; - public static final String SECURITY_KERBEROS_ACCEPTOR_PRINCIPAL = "plugins.security.kerberos.acceptor_principal"; - public static final String SECURITY_CERT_OID = "plugins.security.cert.oid"; - public static final String SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = - "plugins.security.cert.intercluster_request_evaluator_class"; - public static final String SECURITY_ADVANCED_MODULES_ENABLED = "plugins.security.advanced_modules_enabled"; - public static final String SECURITY_NODES_DN = "plugins.security.nodes_dn"; - public static final String SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED = "plugins.security.nodes_dn_dynamic_config_enabled"; - public static final String SECURITY_DISABLED = "plugins.security.disabled"; + public static final String SECURITY_KERBEROS_KRB5_FILEPATH = SECURITY_SETTINGS_PREFIX + "kerberos.krb5_filepath"; + public static final String SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH = SECURITY_SETTINGS_PREFIX + "kerberos.acceptor_keytab_filepath"; + public static final String SECURITY_KERBEROS_ACCEPTOR_PRINCIPAL = SECURITY_SETTINGS_PREFIX + "kerberos.acceptor_principal"; + public static final String SECURITY_CERT_OID = SECURITY_SETTINGS_PREFIX + "cert.oid"; + public static final String SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = SECURITY_SETTINGS_PREFIX + + "cert.intercluster_request_evaluator_class"; + public static final String SECURITY_ADVANCED_MODULES_ENABLED = SECURITY_SETTINGS_PREFIX + "advanced_modules_enabled"; + public static final String SECURITY_NODES_DN = SECURITY_SETTINGS_PREFIX + "nodes_dn"; + public static final String SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED = SECURITY_SETTINGS_PREFIX + "nodes_dn_dynamic_config_enabled"; + public static final String SECURITY_DISABLED = SECURITY_SETTINGS_PREFIX + "disabled"; - public static final String SECURITY_CACHE_TTL_MINUTES = "plugins.security.cache.ttl_minutes"; - public static final String SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES = "plugins.security.allow_unsafe_democertificates"; - public static final String SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX = "plugins.security.allow_default_init_securityindex"; + public static final String SECURITY_CACHE_TTL_MINUTES = SECURITY_SETTINGS_PREFIX + "cache.ttl_minutes"; + public static final String SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES = SECURITY_SETTINGS_PREFIX + "allow_unsafe_democertificates"; + public static final String SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX = SECURITY_SETTINGS_PREFIX + "allow_default_init_securityindex"; - public static final String SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE = - "plugins.security.allow_default_init_securityindex.use_cluster_state"; + public static final String SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE = SECURITY_SETTINGS_PREFIX + + "allow_default_init_securityindex.use_cluster_state"; - public static final String SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST = - "plugins.security.background_init_if_securityindex_not_exist"; + public static final String SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST = SECURITY_SETTINGS_PREFIX + + "background_init_if_securityindex_not_exist"; - public static final String SECURITY_ROLES_MAPPING_RESOLUTION = "plugins.security.roles_mapping_resolution"; + public static final String SECURITY_ROLES_MAPPING_RESOLUTION = SECURITY_SETTINGS_PREFIX + "roles_mapping_resolution"; public static final String OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY = "opendistro_security.compliance.history.write.metadata_only"; @@ -280,21 +283,22 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED = "opendistro_security.compliance.history.external_config_enabled"; public static final String OPENDISTRO_SECURITY_SOURCE_FIELD_CONTEXT = OPENDISTRO_SECURITY_CONFIG_PREFIX + "source_field_context"; - public static final String SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION = - "plugins.security.compliance.disable_anonymous_authentication"; - public static final String SECURITY_COMPLIANCE_IMMUTABLE_INDICES = "plugins.security.compliance.immutable_indices"; - public static final String SECURITY_COMPLIANCE_SALT = "plugins.security.compliance.salt"; + public static final String SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION = SECURITY_SETTINGS_PREFIX + + "compliance.disable_anonymous_authentication"; + public static final String SECURITY_COMPLIANCE_IMMUTABLE_INDICES = SECURITY_SETTINGS_PREFIX + "compliance.immutable_indices"; + public static final String SECURITY_COMPLIANCE_SALT = SECURITY_SETTINGS_PREFIX + "compliance.salt"; public static final String SECURITY_COMPLIANCE_SALT_DEFAULT = "e1ukloTsQlOgPquJ";// 16 chars public static final String SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED = "opendistro_security.compliance.history.internal_config_enabled"; - public static final String SECURITY_SSL_ONLY = "plugins.security.ssl_only"; + public static final String SECURITY_SSL_ONLY = SECURITY_SETTINGS_PREFIX + "ssl_only"; public static final String SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED = "plugins.security_config.ssl_dual_mode_enabled"; public static final String SECURITY_SSL_DUAL_MODE_SKIP_SECURITY = OPENDISTRO_SECURITY_CONFIG_PREFIX + "passive_security"; public static final String LEGACY_OPENDISTRO_SECURITY_CONFIG_SSL_DUAL_MODE_ENABLED = "opendistro_security_config.ssl_dual_mode_enabled"; - public static final String SECURITY_SSL_CERT_RELOAD_ENABLED = "plugins.security.ssl_cert_reload_enabled"; - public static final String SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED = "plugins.security.ssl.certificates_hot_reload.enabled"; - public static final String SECURITY_DISABLE_ENVVAR_REPLACEMENT = "plugins.security.disable_envvar_replacement"; - public static final String SECURITY_DFM_EMPTY_OVERRIDES_ALL = "plugins.security.dfm_empty_overrides_all"; + public static final String SECURITY_SSL_CERT_RELOAD_ENABLED = SECURITY_SETTINGS_PREFIX + "ssl_cert_reload_enabled"; + public static final String SECURITY_SSL_CERTIFICATES_HOT_RELOAD_ENABLED = SECURITY_SETTINGS_PREFIX + + "ssl.certificates_hot_reload.enabled"; + public static final String SECURITY_DISABLE_ENVVAR_REPLACEMENT = SECURITY_SETTINGS_PREFIX + "disable_envvar_replacement"; + public static final String SECURITY_DFM_EMPTY_OVERRIDES_ALL = SECURITY_SETTINGS_PREFIX + "dfm_empty_overrides_all"; public enum RolesMappingResolution { MAPPING_ONLY, @@ -302,43 +306,45 @@ public enum RolesMappingResolution { BOTH } - public static final String SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS = "plugins.security.filter_securityindex_from_all_requests"; - public static final String SECURITY_DLS_MODE = "plugins.security.dls.mode"; + public static final String SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS = SECURITY_SETTINGS_PREFIX + + "filter_securityindex_from_all_requests"; + public static final String SECURITY_DLS_MODE = SECURITY_SETTINGS_PREFIX + "dls.mode"; // REST API - public static final String SECURITY_RESTAPI_ROLES_ENABLED = "plugins.security.restapi.roles_enabled"; - public static final String SECURITY_RESTAPI_ADMIN_ENABLED = "plugins.security.restapi.admin.enabled"; - public static final String SECURITY_RESTAPI_ENDPOINTS_DISABLED = "plugins.security.restapi.endpoints_disabled"; - public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX = "plugins.security.restapi.password_validation_regex"; - public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE = - "plugins.security.restapi.password_validation_error_message"; - public static final String SECURITY_RESTAPI_PASSWORD_MIN_LENGTH = "plugins.security.restapi.password_min_length"; - public static final String SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH = - "plugins.security.restapi.password_score_based_validation_strength"; + public static final String SECURITY_RESTAPI_ROLES_ENABLED = SECURITY_SETTINGS_PREFIX + "restapi.roles_enabled"; + public static final String SECURITY_RESTAPI_ADMIN_ENABLED = SECURITY_SETTINGS_PREFIX + "restapi.admin.enabled"; + public static final String SECURITY_RESTAPI_ENDPOINTS_DISABLED = SECURITY_SETTINGS_PREFIX + "restapi.endpoints_disabled"; + public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX = SECURITY_SETTINGS_PREFIX + "restapi.password_validation_regex"; + public static final String SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE = SECURITY_SETTINGS_PREFIX + + "restapi.password_validation_error_message"; + public static final String SECURITY_RESTAPI_PASSWORD_MIN_LENGTH = SECURITY_SETTINGS_PREFIX + "restapi.password_min_length"; + public static final String SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH = SECURITY_SETTINGS_PREFIX + + "restapi.password_score_based_validation_strength"; // Illegal Opcodes from here on - public static final String SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY = - "plugins.security.unsupported.disable_rest_auth_initially"; - public static final String SECURITY_UNSUPPORTED_DELAY_INITIALIZATION_SECONDS = - "plugins.security.unsupported.delay_initialization_seconds"; - public static final String SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY = - "plugins.security.unsupported.disable_intertransport_auth_initially"; - public static final String SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY = - "plugins.security.unsupported.passive_intertransport_auth_initially"; - public static final String SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED = - "plugins.security.unsupported.restore.securityindex.enabled"; - public static final String SECURITY_UNSUPPORTED_INJECT_USER_ENABLED = "plugins.security.unsupported.inject_user.enabled"; - public static final String SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED = "plugins.security.unsupported.inject_user.admin.enabled"; - public static final String SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS = "plugins.security.unsupported.allow_now_in_dls"; - - public static final String SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION = - "plugins.security.unsupported.restapi.allow_securityconfig_modification"; - public static final String SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES = "plugins.security.unsupported.load_static_resources"; - public static final String SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG = "plugins.security.unsupported.accept_invalid_config"; - - public static final String SECURITY_PROTECTED_INDICES_ENABLED_KEY = "plugins.security.protected_indices.enabled"; + public static final String SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY = SECURITY_SETTINGS_PREFIX + + "unsupported.disable_rest_auth_initially"; + public static final String SECURITY_UNSUPPORTED_DELAY_INITIALIZATION_SECONDS = SECURITY_SETTINGS_PREFIX + + "unsupported.delay_initialization_seconds"; + public static final String SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY = SECURITY_SETTINGS_PREFIX + + "unsupported.disable_intertransport_auth_initially"; + public static final String SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY = SECURITY_SETTINGS_PREFIX + + "unsupported.passive_intertransport_auth_initially"; + public static final String SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED = SECURITY_SETTINGS_PREFIX + + "unsupported.restore.securityindex.enabled"; + public static final String SECURITY_UNSUPPORTED_INJECT_USER_ENABLED = SECURITY_SETTINGS_PREFIX + "unsupported.inject_user.enabled"; + public static final String SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED = SECURITY_SETTINGS_PREFIX + + "unsupported.inject_user.admin.enabled"; + public static final String SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS = SECURITY_SETTINGS_PREFIX + "unsupported.allow_now_in_dls"; + + public static final String SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION = SECURITY_SETTINGS_PREFIX + + "unsupported.restapi.allow_securityconfig_modification"; + public static final String SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES = SECURITY_SETTINGS_PREFIX + "unsupported.load_static_resources"; + public static final String SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG = SECURITY_SETTINGS_PREFIX + "unsupported.accept_invalid_config"; + + public static final String SECURITY_PROTECTED_INDICES_ENABLED_KEY = SECURITY_SETTINGS_PREFIX + "protected_indices.enabled"; public static final Boolean SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT = false; - public static final String SECURITY_PROTECTED_INDICES_KEY = "plugins.security.protected_indices.indices"; + public static final String SECURITY_PROTECTED_INDICES_KEY = SECURITY_SETTINGS_PREFIX + "protected_indices.indices"; public static final List SECURITY_PROTECTED_INDICES_DEFAULT = Collections.emptyList(); - public static final String SECURITY_PROTECTED_INDICES_ROLES_KEY = "plugins.security.protected_indices.roles"; + public static final String SECURITY_PROTECTED_INDICES_ROLES_KEY = SECURITY_SETTINGS_PREFIX + "protected_indices.roles"; public static final List SECURITY_PROTECTED_INDICES_ROLES_DEFAULT = Collections.emptyList(); // Roles injection for plugins @@ -352,19 +358,20 @@ public enum RolesMappingResolution { // System indices settings public static final String SYSTEM_INDEX_PERMISSION = "system:admin/system_index"; - public static final String SECURITY_SYSTEM_INDICES_ENABLED_KEY = "plugins.security.system_indices.enabled"; + public static final String SECURITY_SYSTEM_INDICES_ENABLED_KEY = SECURITY_SETTINGS_PREFIX + "system_indices.enabled"; public static final Boolean SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT = false; - public static final String SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY = "plugins.security.system_indices.permission.enabled"; + public static final String SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY = SECURITY_SETTINGS_PREFIX + + "system_indices.permission.enabled"; public static final Boolean SECURITY_SYSTEM_INDICES_PERMISSIONS_DEFAULT = false; - public static final String SECURITY_SYSTEM_INDICES_KEY = "plugins.security.system_indices.indices"; + public static final String SECURITY_SYSTEM_INDICES_KEY = SECURITY_SETTINGS_PREFIX + "system_indices.indices"; public static final List SECURITY_SYSTEM_INDICES_DEFAULT = Collections.emptyList(); - public static final String SECURITY_MASKED_FIELDS_ALGORITHM_DEFAULT = "plugins.security.masked_fields.algorithm.default"; + public static final String SECURITY_MASKED_FIELDS_ALGORITHM_DEFAULT = SECURITY_SETTINGS_PREFIX + "masked_fields.algorithm.default"; public static final String TENANCY_PRIVATE_TENANT_NAME = "private"; public static final String TENANCY_GLOBAL_TENANT_NAME = "global"; public static final String TENANCY_GLOBAL_TENANT_DEFAULT_NAME = ""; - public static final String USE_JDK_SERIALIZATION = "plugins.security.use_jdk_serialization"; + public static final String USE_JDK_SERIALIZATION = SECURITY_SETTINGS_PREFIX + "use_jdk_serialization"; // On-behalf-of endpoints settings // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings From 337997436449bc0d5bc0c502a69aea67d97375f7 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 20 Mar 2025 13:49:07 -0400 Subject: [PATCH 02/15] Renames couple of SPI classes Signed-off-by: Darshit Chanpura --- .../security/spi/resources/ResourceSharingExtension.java | 2 +- .../resources/{Resource.java => ShareableResource.java} | 8 ++++---- .../{ResourceParser.java => ShareableResourceParser.java} | 8 ++++---- .../security/spi/resources/sharing/ResourceSharing.java | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) rename spi/src/main/java/org/opensearch/security/spi/resources/{Resource.java => ShareableResource.java} (74%) rename spi/src/main/java/org/opensearch/security/spi/resources/{ResourceParser.java => ShareableResourceParser.java} (68%) diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java index bbfc802d82..47aab11136 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceSharingExtension.java @@ -31,5 +31,5 @@ public interface ResourceSharingExtension { * The parser for the resource, which will be used by security plugin to parse the resource * @return the parser for the resource */ - ResourceParser getResourceParser(); + ShareableResourceParser getShareableResourceParser(); } diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java b/spi/src/main/java/org/opensearch/security/spi/resources/ShareableResource.java similarity index 74% rename from spi/src/main/java/org/opensearch/security/spi/resources/Resource.java rename to spi/src/main/java/org/opensearch/security/spi/resources/ShareableResource.java index 72e0b7b5d1..aa22a72337 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/Resource.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ShareableResource.java @@ -12,16 +12,16 @@ import org.opensearch.core.xcontent.ToXContentFragment; /** - * Marker interface for all resources + * Marker interface for all shareable resources * * @opensearch.experimental */ -public interface Resource extends NamedWriteable, ToXContentFragment { +public interface ShareableResource extends NamedWriteable, ToXContentFragment { /** * Abstract method to get the resource name. * Must be implemented by plugins defining resources. * - * @return resource name + * @return the resource name */ - String getResourceName(); + String getName(); } diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java b/spi/src/main/java/org/opensearch/security/spi/resources/ShareableResourceParser.java similarity index 68% rename from spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java rename to spi/src/main/java/org/opensearch/security/spi/resources/ShareableResourceParser.java index b02269322e..4128c427d4 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceParser.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ShareableResourceParser.java @@ -13,16 +13,16 @@ import org.opensearch.core.xcontent.XContentParser; /** - * Interface for parsing resources from XContentParser + * Interface for parsing shareable resources from XContentParser * @param the type of resource to be parsed * * @opensearch.experimental */ -public interface ResourceParser { +public interface ShareableResourceParser { /** - * Parse source bytes supplied by the parser to a desired Resource type + * Parse source bytes supplied by the parser to a desired ShareableResource type * @param parser to parser bytes-ref json input - * @return the parsed object of Resource type + * @return the parsed object of ShareableResource type * @throws IOException if something went wrong while parsing */ T parseXContent(XContentParser parser) throws IOException; diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java index 1690213872..3e00bc5d29 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java @@ -111,7 +111,7 @@ public int hashCode() { @Override public String toString() { - return "Resource {" + return "ShareableResource {" + "sourceIdx='" + sourceIdx + '\'' From e17996817174a29eafe6c1ca29b360d0c104304c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 20 Mar 2025 16:18:15 -0400 Subject: [PATCH 03/15] Updates scope name Signed-off-by: Darshit Chanpura --- .../security/spi/resources/ResourceAccessScope.java | 4 ++-- .../security/spi/resources/ShareWithTests.java | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java index c3b54a8c23..6521ce7d90 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java @@ -18,8 +18,8 @@ * @opensearch.experimental */ public interface ResourceAccessScope> { - String RESTRICTED = "restricted"; - String PUBLIC = "public"; + String READ_ONLY = "read_only"; + String PUBLIC = "public"; // users: ["*"], roles: ["*"], backend_roles: ["*"] static & ResourceAccessScope> E fromValue(Class enumClass, String value) { for (E enumConstant : enumClass.getEnumConstants()) { diff --git a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java index d7ffa0ce5e..3fce191637 100644 --- a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java +++ b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java @@ -103,15 +103,15 @@ public void testFromXContentWithStartObject() throws IOException { XContentParser parser; try (XContentBuilder builder = XContentFactory.jsonBuilder()) { builder.startObject() - .startObject(ResourceAccessScope.RESTRICTED) + .startObject(ResourceAccessScope.READ_ONLY) .array("users", "user1", "user2") .array("roles", "role1") .array("backend_roles", "backend_role1") .endObject() .startObject(ResourceAccessScope.PUBLIC) - .array("users", "user3") - .array("roles", "role2", "role3") - .array("backend_roles") + .array("users", "*") + .array("roles", "*") + .array("backend_roles", "*") .endObject() .endObject(); @@ -129,7 +129,7 @@ public void testFromXContentWithStartObject() throws IOException { for (SharedWithScope scope : scopes) { SharedWithScope.ScopeRecipients perScope = scope.getSharedWithPerScope(); Map> recipients = perScope.getRecipients(); - if (scope.getScope().equals(ResourceAccessScope.RESTRICTED)) { + if (scope.getScope().equals(ResourceAccessScope.READ_ONLY)) { MatcherAssert.assertThat( recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), is(2) @@ -250,7 +250,7 @@ public void test_writeSharedWithScopesToStream() throws IOException { StreamOutput mockStreamOutput = Mockito.mock(StreamOutput.class); Set sharedWithScopes = new HashSet<>(); - sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.RESTRICTED, new SharedWithScope.ScopeRecipients(Map.of()))); + sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.READ_ONLY, new SharedWithScope.ScopeRecipients(Map.of()))); sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.PUBLIC, new SharedWithScope.ScopeRecipients(Map.of()))); ShareWith shareWith = new ShareWith(sharedWithScopes); From 63c4a27fed69459d23d8d4521a1026e428d36e94 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 20 Mar 2025 16:36:57 -0400 Subject: [PATCH 04/15] Fixes CI Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fcfda4926..bdf34afd76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -233,6 +233,7 @@ jobs: steps: - name: Run start commands run: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-start-command }} + - name: Set up JDK for build and test uses: actions/setup-java@v4 with: @@ -258,7 +259,7 @@ jobs: spi-tests-linux: name: spi-tests - needs: publish-components-to-maven-local + needs: ["Get-CI-Image-Tag", publish-components-to-maven-local] strategy: fail-fast: false matrix: @@ -273,6 +274,9 @@ jobs: options: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-start-options }} steps: + - name: Run start commands + run: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-start-command }} + - name: Set up JDK for build and test uses: actions/setup-java@v4 with: From 2fd2a8bf2ab62c58d49d0bd7c516486d3af07d62 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 20 Mar 2025 16:46:43 -0400 Subject: [PATCH 05/15] Fixes test Signed-off-by: Darshit Chanpura --- .../org/opensearch/security/spi/resources/ShareWithTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java index 3fce191637..0e89e04f1c 100644 --- a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java +++ b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java @@ -149,11 +149,11 @@ public void testFromXContentWithStartObject() throws IOException { ); MatcherAssert.assertThat( recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), - is(2) + is(1) ); MatcherAssert.assertThat( recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), - is(0) + is(1) ); } } From eaa92b0c098474053104dfc29e46913753721a94 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 21 Mar 2025 18:17:11 -0400 Subject: [PATCH 06/15] Adds new exception types and updated README.md Signed-off-by: Darshit Chanpura --- spi/README.md | 3 +- .../ResourceNonSystemIndexException.java | 46 ++++++++++++++ .../exceptions/ResourceNotFoundException.java | 46 ++++++++++++++ .../exceptions/ResourceSharingException.java | 15 +---- ...sourceSharingFeatureDisabledException.java | 60 +++++++++++++++++++ ...nauthenticatedResourceAccessException.java | 46 ++++++++++++++ .../UnauthorizedResourceAccessException.java | 46 ++++++++++++++ 7 files changed, 247 insertions(+), 15 deletions(-) create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNonSystemIndexException.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNotFoundException.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthenticatedResourceAccessException.java create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthorizedResourceAccessException.java diff --git a/spi/README.md b/spi/README.md index 2d4d13f989..1a715ed180 100644 --- a/spi/README.md +++ b/spi/README.md @@ -137,7 +137,8 @@ Use the **client API methods** to manage resource sharing. #### **Example: Verifying Resource Access** ```java -Set scopes = Set.of("READ_ONLY"); +Set scopes = Set.of("read_only"); +ResourceSharingClient resourceSharingClient = ResourceSharingClientAccessor.getResourceSharingClient(nodeClient, settings); resourceSharingClient.verifyResourceAccess( "resource-123", "resource_index", diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNonSystemIndexException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNonSystemIndexException.java new file mode 100644 index 0000000000..fb8d5af2c8 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNonSystemIndexException.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.spi.resources.exceptions; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.rest.RestStatus; + +/** + * This class represents an exception that is caused when a resource-index is registered as non-system index. + * It extends the ResourceSharingException class. + * + * @opensearch.experimental + */ +public final class ResourceNonSystemIndexException extends ResourceSharingException { + public ResourceNonSystemIndexException(Throwable cause) { + super(cause); + } + + public ResourceNonSystemIndexException(String msg, Object... args) { + super(msg, args); + } + + public ResourceNonSystemIndexException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } + + public ResourceNonSystemIndexException(StreamInput in) throws IOException { + super(in); + } + + @Override + public RestStatus status() { + return RestStatus.BAD_REQUEST; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNotFoundException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNotFoundException.java new file mode 100644 index 0000000000..67ffb8a411 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNotFoundException.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.spi.resources.exceptions; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.rest.RestStatus; + +/** + * This class represents an exception that occurs when resource sharing feature is disabled or security plugin is disabled. + * It extends the ResourceSharingException class. + * + * @opensearch.experimental + */ +public final class ResourceNotFoundException extends ResourceSharingException { + public ResourceNotFoundException(Throwable cause) { + super(cause); + } + + public ResourceNotFoundException(String msg, Object... args) { + super(msg, args); + } + + public ResourceNotFoundException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } + + public ResourceNotFoundException(StreamInput in) throws IOException { + super(in); + } + + @Override + public RestStatus status() { + return RestStatus.NOT_FOUND; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java index 560669112b..46d583e1e2 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java @@ -18,7 +18,7 @@ import org.opensearch.core.rest.RestStatus; /** - * This class represents an exception that occurs during resource sharing operations. + * This class represents a generic exception that occurs during resource sharing operations. * It extends the OpenSearchException class. * * @opensearch.experimental @@ -42,19 +42,6 @@ public ResourceSharingException(StreamInput in) throws IOException { @Override public RestStatus status() { - String message = getMessage(); - if (message.contains("not authorized")) { - return RestStatus.FORBIDDEN; - } else if (message.startsWith("No authenticated")) { - return RestStatus.UNAUTHORIZED; - } else if (message.contains("not found")) { - return RestStatus.NOT_FOUND; - } else if (message.contains("not a system index")) { - return RestStatus.BAD_REQUEST; - } else if (message.contains("is disabled")) { - return RestStatus.NOT_IMPLEMENTED; - } - return RestStatus.INTERNAL_SERVER_ERROR; } } diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java new file mode 100644 index 0000000000..6a7dcc8291 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.spi.resources.exceptions; + +import java.io.IOException; + +import org.opensearch.OpenSearchException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.rest.RestStatus; + +/** + * This class represents an exception that occurs during resource sharing operations. + * It extends the OpenSearchException class. + * + * @opensearch.experimental + */ +public final class ResourceSharingFeatureDisabledException extends OpenSearchException { + public ResourceSharingFeatureDisabledException(Throwable cause) { + super(cause); + } + + public ResourceSharingFeatureDisabledException(String msg, Object... args) { + super(msg, args); + } + + public ResourceSharingFeatureDisabledException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } + + public ResourceSharingFeatureDisabledException(StreamInput in) throws IOException { + super(in); + } + + @Override + public RestStatus status() { + String message = getMessage(); + if (message.contains("not authorized")) { + return RestStatus.FORBIDDEN; + } else if (message.startsWith("No authenticated")) { + return RestStatus.UNAUTHORIZED; + } else if (message.contains("not found")) { + return RestStatus.NOT_FOUND; + } else if (message.contains("not a system index")) { + return RestStatus.BAD_REQUEST; + } else if (message.contains("is disabled")) { + return RestStatus.NOT_IMPLEMENTED; + } + + return RestStatus.INTERNAL_SERVER_ERROR; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthenticatedResourceAccessException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthenticatedResourceAccessException.java new file mode 100644 index 0000000000..634a910b21 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthenticatedResourceAccessException.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.spi.resources.exceptions; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.rest.RestStatus; + +/** + * This class represents an exception that occurs when an unauthenticated user tries to access a resource. + * It extends the ResourceSharingException class. + * + * @opensearch.experimental + */ +public final class UnauthenticatedResourceAccessException extends ResourceSharingException { + public UnauthenticatedResourceAccessException(Throwable cause) { + super(cause); + } + + public UnauthenticatedResourceAccessException(String msg, Object... args) { + super(msg, args); + } + + public UnauthenticatedResourceAccessException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } + + public UnauthenticatedResourceAccessException(StreamInput in) throws IOException { + super(in); + } + + @Override + public RestStatus status() { + return RestStatus.UNAUTHORIZED; + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthorizedResourceAccessException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthorizedResourceAccessException.java new file mode 100644 index 0000000000..888a0af7d7 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthorizedResourceAccessException.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.spi.resources.exceptions; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.rest.RestStatus; + +/** + * This class represents an exception that occurs when an unauthorized user tries to access the resource. + * It extends the ResourceSharingException class. + * + * @opensearch.experimental + */ +public final class UnauthorizedResourceAccessException extends ResourceSharingException { + public UnauthorizedResourceAccessException(Throwable cause) { + super(cause); + } + + public UnauthorizedResourceAccessException(String msg, Object... args) { + super(msg, args); + } + + public UnauthorizedResourceAccessException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } + + public UnauthorizedResourceAccessException(StreamInput in) throws IOException { + super(in); + } + + @Override + public RestStatus status() { + return RestStatus.FORBIDDEN; + } +} From f3d13ee5742390735d64e3ddc3a24c4e3bb75123 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 26 Mar 2025 16:55:43 -0400 Subject: [PATCH 07/15] Refactors scopes to action-groups and updates SPI Signed-off-by: Darshit Chanpura --- spi/README.md | 2 - spi/build.gradle | 6 +- .../resources/ResourceAccessActionGroups.java | 37 +++++++++ .../spi/resources/ResourceAccessScope.java | 38 ---------- .../resources/sharing/ResourceSharing.java | 2 +- .../spi/resources/sharing/ShareWith.java | 57 +++++++------- ...hScope.java => SharedWithActionGroup.java} | 66 ++++++++-------- .../spi/resources/ShareWithTests.java | 75 ++++++++++--------- 8 files changed, 143 insertions(+), 140 deletions(-) create mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessActionGroups.java delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java rename spi/src/main/java/org/opensearch/security/spi/resources/sharing/{SharedWithScope.java => SharedWithActionGroup.java} (64%) diff --git a/spi/README.md b/spi/README.md index 1a715ed180..9c1bdf5a7f 100644 --- a/spi/README.md +++ b/spi/README.md @@ -137,12 +137,10 @@ Use the **client API methods** to manage resource sharing. #### **Example: Verifying Resource Access** ```java -Set scopes = Set.of("read_only"); ResourceSharingClient resourceSharingClient = ResourceSharingClientAccessor.getResourceSharingClient(nodeClient, settings); resourceSharingClient.verifyResourceAccess( "resource-123", "resource_index", - scopes, ActionListener.wrap(isAuthorized -> { if (isAuthorized) { System.out.println("User has access to the resource."); diff --git a/spi/build.gradle b/spi/build.gradle index b8f33319b3..7fdd56ce51 100644 --- a/spi/build.gradle +++ b/spi/build.gradle @@ -10,7 +10,7 @@ plugins { } ext { - opensearch_version = System.getProperty("opensearch.version", "3.0.0-alpha1-SNAPSHOT") + opensearch_version = System.getProperty("opensearch.version", "3.0.0-beta1-SNAPSHOT") } repositories { @@ -28,6 +28,10 @@ java { targetCompatibility = JavaVersion.VERSION_21 } +shadowJar { + archiveClassifier.set(null) +} + task sourcesJar(type: Jar) { archiveClassifier.set 'sources' from sourceSets.main.allJava diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessActionGroups.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessActionGroups.java new file mode 100644 index 0000000000..2289ce5676 --- /dev/null +++ b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessActionGroups.java @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.security.spi.resources; + +import java.util.Arrays; + +/** + * This class represents action-groups to be utilized to share resources. + * + * @opensearch.experimental + */ +public interface ResourceAccessActionGroups> { + // TODO update following comment once ResourceAuthz is implemented as a standalone framework + // At present, we define this place-holder value represents the default action group this resource is shared with. + String PLACE_HOLDER = "default"; + + static & ResourceAccessActionGroups> E fromValue(Class enumClass, String value) { + for (E enumConstant : enumClass.getEnumConstants()) { + if (enumConstant.value().equalsIgnoreCase(value)) { + return enumConstant; + } + } + throw new IllegalArgumentException("Unknown value: " + value); + } + + String value(); + + static & ResourceAccessActionGroups> String[] values(Class enumClass) { + return Arrays.stream(enumClass.getEnumConstants()).map(ResourceAccessActionGroups::value).toArray(String[]::new); + } +} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java b/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java deleted file mode 100644 index 6521ce7d90..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/ResourceAccessScope.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.security.spi.resources; - -import java.util.Arrays; - -/** - * This interface defines the two basic access scopes for resource-access. Plugins can decide whether to use these. - * Each plugin must implement their own scopes and manage them. - * These access scopes will then be used to verify the type of access being requested. - * - * @opensearch.experimental - */ -public interface ResourceAccessScope> { - String READ_ONLY = "read_only"; - String PUBLIC = "public"; // users: ["*"], roles: ["*"], backend_roles: ["*"] - - static & ResourceAccessScope> E fromValue(Class enumClass, String value) { - for (E enumConstant : enumClass.getEnumConstants()) { - if (enumConstant.value().equalsIgnoreCase(value)) { - return enumConstant; - } - } - throw new IllegalArgumentException("Unknown value: " + value); - } - - String value(); - - static & ResourceAccessScope> String[] values(Class enumClass) { - return Arrays.stream(enumClass.getEnumConstants()).map(ResourceAccessScope::value).toArray(String[]::new); - } -} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java index 3e00bc5d29..61f43a646e 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ResourceSharing.java @@ -147,7 +147,7 @@ public void writeTo(StreamOutput out) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject().field("source_idx", sourceIdx).field("resource_id", resourceId).field("created_by"); createdBy.toXContent(builder, params); - if (shareWith != null && !shareWith.getSharedWithScopes().isEmpty()) { + if (shareWith != null && !shareWith.getSharedWithActionGroups().isEmpty()) { builder.field("share_with"); shareWith.toXContent(builder, params); } diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ShareWith.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ShareWith.java index 267bb7ece0..926007c0d5 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ShareWith.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/ShareWith.java @@ -20,55 +20,56 @@ import org.opensearch.core.xcontent.XContentParser; /** - * This class contains information about whom a resource is shared with and at what scope. - * Example: + * This class contains information about whom a resource is shared with and what is the action-group associated with it. + * + *

Example usage: + *

  * "share_with": {
- * "read_only": {
- * "users": [],
- * "roles": [],
- * "backend_roles": []
- * },
- * "read_write": {
- * "users": [],
- * "roles": [],
- * "backend_roles": []
- * }
+ *   "default": {
+ *     "users": [],
+ *     "roles": [],
+ *     "backend_roles": []
+ *   }
  * }
+ * 
+ * + * "default" is a place-holder {@link org.opensearch.security.spi.resources.ResourceAccessActionGroups#PLACE_HOLDER } that must be replaced with action-group names once Resource Authorization framework is implemented. * * @opensearch.experimental */ + public class ShareWith implements ToXContentFragment, NamedWriteable { /** - * A set of objects representing the scopes and their associated users, roles, and backend roles. + * A set of objects representing the action-groups and their associated users, roles, and backend roles. */ - private final Set sharedWithScopes; + private final Set sharedWithActionGroups; - public ShareWith(Set sharedWithScopes) { - this.sharedWithScopes = sharedWithScopes; + public ShareWith(Set sharedWithActionGroups) { + this.sharedWithActionGroups = sharedWithActionGroups; } public ShareWith(StreamInput in) throws IOException { - this.sharedWithScopes = in.readSet(SharedWithScope::new); + this.sharedWithActionGroups = in.readSet(SharedWithActionGroup::new); } - public Set getSharedWithScopes() { - return sharedWithScopes; + public Set getSharedWithActionGroups() { + return sharedWithActionGroups; } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); - for (SharedWithScope scope : sharedWithScopes) { - scope.toXContent(builder, params); + for (SharedWithActionGroup actionGroup : sharedWithActionGroups) { + actionGroup.toXContent(builder, params); } return builder.endObject(); } public static ShareWith fromXContent(XContentParser parser) throws IOException { - Set sharedWithScopes = new HashSet<>(); + Set sharedWithActionGroups = new HashSet<>(); if (parser.currentToken() != XContentParser.Token.START_OBJECT) { parser.nextToken(); @@ -76,14 +77,14 @@ public static ShareWith fromXContent(XContentParser parser) throws IOException { XContentParser.Token token; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { - // Each field in the object represents a SharedWithScope + // Each field in the object represents a SharedWithActionGroup if (token == XContentParser.Token.FIELD_NAME) { - SharedWithScope scope = SharedWithScope.fromXContent(parser); - sharedWithScopes.add(scope); + SharedWithActionGroup actionGroup = SharedWithActionGroup.fromXContent(parser); + sharedWithActionGroups.add(actionGroup); } } - return new ShareWith(sharedWithScopes); + return new ShareWith(sharedWithActionGroups); } @Override @@ -93,11 +94,11 @@ public String getWriteableName() { @Override public void writeTo(StreamOutput out) throws IOException { - out.writeCollection(sharedWithScopes); + out.writeCollection(sharedWithActionGroups); } @Override public String toString() { - return "ShareWith " + sharedWithScopes; + return "ShareWith " + sharedWithActionGroups; } } diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithScope.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithActionGroup.java similarity index 64% rename from spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithScope.java rename to spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithActionGroup.java index 1dfca103a3..5d06d17de7 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithScope.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithActionGroup.java @@ -22,94 +22,94 @@ import org.opensearch.core.xcontent.XContentParser; /** - * This class represents the scope at which a resource is shared with for a particular scope. + * This class represents the entities with which a resource is shared for a particular action-group. * Example: - * "read_only": { + * "default": { * "users": [], * "roles": [], * "backend_roles": [] * } - * where "users", "roles" and "backend_roles" are the recipient entities + * where "users", "roles" and "backend_roles" are the recipient entities, and "default" is the action-group * * @opensearch.experimental */ -public class SharedWithScope implements ToXContentFragment, NamedWriteable { +public class SharedWithActionGroup implements ToXContentFragment, NamedWriteable { - private final String scope; + private final String actionGroup; - private final ScopeRecipients scopeRecipients; + private final ActionGroupRecipients actionGroupRecipients; - public SharedWithScope(String scope, ScopeRecipients scopeRecipients) { - this.scope = scope; - this.scopeRecipients = scopeRecipients; + public SharedWithActionGroup(String actionGroup, ActionGroupRecipients actionGroupRecipients) { + this.actionGroup = actionGroup; + this.actionGroupRecipients = actionGroupRecipients; } - public SharedWithScope(StreamInput in) throws IOException { - this.scope = in.readString(); - this.scopeRecipients = new ScopeRecipients(in); + public SharedWithActionGroup(StreamInput in) throws IOException { + this.actionGroup = in.readString(); + this.actionGroupRecipients = new ActionGroupRecipients(in); } - public String getScope() { - return scope; + public String getActionGroup() { + return actionGroup; } - public ScopeRecipients getSharedWithPerScope() { - return scopeRecipients; + public ActionGroupRecipients getSharedWithPerActionGroup() { + return actionGroupRecipients; } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.field(scope); + builder.field(actionGroup); builder.startObject(); - scopeRecipients.toXContent(builder, params); + actionGroupRecipients.toXContent(builder, params); return builder.endObject(); } - public static SharedWithScope fromXContent(XContentParser parser) throws IOException { - String scope = parser.currentName(); + public static SharedWithActionGroup fromXContent(XContentParser parser) throws IOException { + String actionGroup = parser.currentName(); parser.nextToken(); - ScopeRecipients scopeRecipients = ScopeRecipients.fromXContent(parser); + ActionGroupRecipients actionGroupRecipients = ActionGroupRecipients.fromXContent(parser); - return new SharedWithScope(scope, scopeRecipients); + return new SharedWithActionGroup(actionGroup, actionGroupRecipients); } @Override public String toString() { - return "{" + scope + ": " + scopeRecipients + '}'; + return "{" + actionGroup + ": " + actionGroupRecipients + '}'; } @Override public String getWriteableName() { - return "shared_with_scope"; + return "shared_with_action_group"; } @Override public void writeTo(StreamOutput out) throws IOException { - out.writeString(scope); - out.writeNamedWriteable(scopeRecipients); + out.writeString(actionGroup); + out.writeNamedWriteable(actionGroupRecipients); } /** - * This class represents the entities with whom a resource is shared with for a given scope. + * This class represents the entities with whom a resource is shared with for a given action-group. * * @opensearch.experimental */ - public static class ScopeRecipients implements ToXContentFragment, NamedWriteable { + public static class ActionGroupRecipients implements ToXContentFragment, NamedWriteable { private final Map> recipients; - public ScopeRecipients(Map> recipients) { + public ActionGroupRecipients(Map> recipients) { if (recipients == null) { throw new IllegalArgumentException("Recipients map cannot be null"); } this.recipients = recipients; } - public ScopeRecipients(StreamInput in) throws IOException { + public ActionGroupRecipients(StreamInput in) throws IOException { this.recipients = in.readMap( key -> RecipientTypeRegistry.fromValue(key.readString()), input -> input.readSet(StreamInput::readString) @@ -122,10 +122,10 @@ public Map> getRecipients() { @Override public String getWriteableName() { - return "scope_recipients"; + return "action_group_recipients"; } - public static ScopeRecipients fromXContent(XContentParser parser) throws IOException { + public static ActionGroupRecipients fromXContent(XContentParser parser) throws IOException { Map> recipients = new HashMap<>(); XContentParser.Token token; @@ -143,7 +143,7 @@ public static ScopeRecipients fromXContent(XContentParser parser) throws IOExcep } } - return new ScopeRecipients(recipients); + return new ActionGroupRecipients(recipients); } @Override diff --git a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java index 0e89e04f1c..c2c271dd24 100644 --- a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java +++ b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java @@ -29,7 +29,7 @@ import org.opensearch.security.spi.resources.sharing.RecipientType; import org.opensearch.security.spi.resources.sharing.RecipientTypeRegistry; import org.opensearch.security.spi.resources.sharing.ShareWith; -import org.opensearch.security.spi.resources.sharing.SharedWithScope; +import org.opensearch.security.spi.resources.sharing.SharedWithActionGroup; import org.mockito.Mockito; @@ -68,16 +68,16 @@ public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOExceptio ShareWith shareWith = ShareWith.fromXContent(parser); MatcherAssert.assertThat(shareWith, notNullValue()); - Set sharedWithScopes = shareWith.getSharedWithScopes(); - MatcherAssert.assertThat(sharedWithScopes, notNullValue()); - MatcherAssert.assertThat(1, equalTo(sharedWithScopes.size())); + Set sharedWithActionGroups = shareWith.getSharedWithActionGroups(); + MatcherAssert.assertThat(sharedWithActionGroups, notNullValue()); + MatcherAssert.assertThat(1, equalTo(sharedWithActionGroups.size())); - SharedWithScope scope = sharedWithScopes.iterator().next(); - MatcherAssert.assertThat("read_only", equalTo(scope.getScope())); + SharedWithActionGroup actionGroup = sharedWithActionGroups.iterator().next(); + MatcherAssert.assertThat("read_only", equalTo(actionGroup.getActionGroup())); - SharedWithScope.ScopeRecipients scopeRecipients = scope.getSharedWithPerScope(); - MatcherAssert.assertThat(scopeRecipients, notNullValue()); - Map> recipients = scopeRecipients.getRecipients(); + SharedWithActionGroup.ActionGroupRecipients actionGroupRecipients = actionGroup.getSharedWithPerActionGroup(); + MatcherAssert.assertThat(actionGroupRecipients, notNullValue()); + Map> recipients = actionGroupRecipients.getRecipients(); MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), is(1)); MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())), contains("user1")); MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), is(0)); @@ -95,7 +95,7 @@ public void testFromXContentWithEmptyInput() throws IOException { ShareWith result = ShareWith.fromXContent(parser); MatcherAssert.assertThat(result, notNullValue()); - MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty())); + MatcherAssert.assertThat(result.getSharedWithActionGroups(), is(empty())); } @Test @@ -103,12 +103,12 @@ public void testFromXContentWithStartObject() throws IOException { XContentParser parser; try (XContentBuilder builder = XContentFactory.jsonBuilder()) { builder.startObject() - .startObject(ResourceAccessScope.READ_ONLY) + .startObject(ResourceAccessActionGroups.PLACE_HOLDER) .array("users", "user1", "user2") .array("roles", "role1") .array("backend_roles", "backend_role1") .endObject() - .startObject(ResourceAccessScope.PUBLIC) + .startObject("random-action-group") .array("users", "*") .array("roles", "*") .array("backend_roles", "*") @@ -123,13 +123,13 @@ public void testFromXContentWithStartObject() throws IOException { ShareWith shareWith = ShareWith.fromXContent(parser); MatcherAssert.assertThat(shareWith, notNullValue()); - Set scopes = shareWith.getSharedWithScopes(); - MatcherAssert.assertThat(scopes.size(), equalTo(2)); + Set actionGroups = shareWith.getSharedWithActionGroups(); + MatcherAssert.assertThat(actionGroups.size(), equalTo(2)); - for (SharedWithScope scope : scopes) { - SharedWithScope.ScopeRecipients perScope = scope.getSharedWithPerScope(); + for (SharedWithActionGroup actionGroup : actionGroups) { + SharedWithActionGroup.ActionGroupRecipients perScope = actionGroup.getSharedWithPerActionGroup(); Map> recipients = perScope.getRecipients(); - if (scope.getScope().equals(ResourceAccessScope.READ_ONLY)) { + if (actionGroup.getActionGroup().equals(ResourceAccessActionGroups.PLACE_HOLDER)) { MatcherAssert.assertThat( recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), is(2) @@ -142,7 +142,7 @@ public void testFromXContentWithStartObject() throws IOException { recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), is(1) ); - } else if (scope.getScope().equals(ResourceAccessScope.PUBLIC)) { + } else if (actionGroup.getActionGroup().equals("random-action-group")) { MatcherAssert.assertThat( recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), is(1) @@ -168,20 +168,20 @@ public void testFromXContentWithUnexpectedEndOfInput() throws IOException { ShareWith result = ShareWith.fromXContent(mockParser); MatcherAssert.assertThat(result, notNullValue()); - MatcherAssert.assertThat(result.getSharedWithScopes(), is(empty())); + MatcherAssert.assertThat(result.getSharedWithActionGroups(), is(empty())); } @Test public void testToXContentBuildsCorrectly() throws IOException { - SharedWithScope scope = new SharedWithScope( - "scope1", - new SharedWithScope.ScopeRecipients(Map.of(new RecipientType("users"), Set.of("bleh"))) + SharedWithActionGroup actionGroup = new SharedWithActionGroup( + "actionGroup1", + new SharedWithActionGroup.ActionGroupRecipients(Map.of(new RecipientType("users"), Set.of("bleh"))) ); - Set scopes = new HashSet<>(); - scopes.add(scope); + Set actionGroups = new HashSet<>(); + actionGroups.add(actionGroup); - ShareWith shareWith = new ShareWith(scopes); + ShareWith shareWith = new ShareWith(actionGroups); XContentBuilder builder = JsonXContent.contentBuilder(); @@ -189,7 +189,7 @@ public void testToXContentBuildsCorrectly() throws IOException { String result = builder.toString(); - String expected = "{\"scope1\":{\"users\":[\"bleh\"]}}"; + String expected = "{\"actionGroup1\":{\"users\":[\"bleh\"]}}"; MatcherAssert.assertThat(expected.length(), equalTo(result.length())); MatcherAssert.assertThat(expected, equalTo(result)); @@ -197,7 +197,7 @@ public void testToXContentBuildsCorrectly() throws IOException { @Test public void testWriteToWithEmptySet() throws IOException { - Set emptySet = Collections.emptySet(); + Set emptySet = Collections.emptySet(); ShareWith shareWith = new ShareWith(emptySet); StreamOutput mockOutput = Mockito.mock(StreamOutput.class); @@ -208,8 +208,8 @@ public void testWriteToWithEmptySet() throws IOException { @Test public void testWriteToWithIOException() throws IOException { - Set set = new HashSet<>(); - set.add(new SharedWithScope("test", new SharedWithScope.ScopeRecipients(Map.of()))); + Set set = new HashSet<>(); + set.add(new SharedWithActionGroup("test", new SharedWithActionGroup.ActionGroupRecipients(Map.of()))); ShareWith shareWith = new ShareWith(set); StreamOutput mockOutput = Mockito.mock(StreamOutput.class); @@ -220,9 +220,9 @@ public void testWriteToWithIOException() throws IOException { @Test public void testWriteToWithLargeSet() throws IOException { - Set largeSet = new HashSet<>(); + Set largeSet = new HashSet<>(); for (int i = 0; i < 10000; i++) { - largeSet.add(new SharedWithScope("scope" + i, new SharedWithScope.ScopeRecipients(Map.of()))); + largeSet.add(new SharedWithActionGroup("actionGroup" + i, new SharedWithActionGroup.ActionGroupRecipients(Map.of()))); } ShareWith shareWith = new ShareWith(largeSet); StreamOutput mockOutput = Mockito.mock(StreamOutput.class); @@ -242,22 +242,23 @@ public void test_fromXContent_emptyObject() throws IOException { ShareWith shareWith = ShareWith.fromXContent(parser); - MatcherAssert.assertThat(shareWith.getSharedWithScopes(), is(empty())); + MatcherAssert.assertThat(shareWith.getSharedWithActionGroups(), is(empty())); } @Test public void test_writeSharedWithScopesToStream() throws IOException { StreamOutput mockStreamOutput = Mockito.mock(StreamOutput.class); - Set sharedWithScopes = new HashSet<>(); - sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.READ_ONLY, new SharedWithScope.ScopeRecipients(Map.of()))); - sharedWithScopes.add(new SharedWithScope(ResourceAccessScope.PUBLIC, new SharedWithScope.ScopeRecipients(Map.of()))); + Set sharedWithActionGroups = new HashSet<>(); + sharedWithActionGroups.add( + new SharedWithActionGroup(ResourceAccessActionGroups.PLACE_HOLDER, new SharedWithActionGroup.ActionGroupRecipients(Map.of())) + ); - ShareWith shareWith = new ShareWith(sharedWithScopes); + ShareWith shareWith = new ShareWith(sharedWithActionGroups); shareWith.writeTo(mockStreamOutput); - verify(mockStreamOutput, times(1)).writeCollection(eq(sharedWithScopes)); + verify(mockStreamOutput, times(1)).writeCollection(eq(sharedWithActionGroups)); } private void initializeRecipientTypes() { From 806d7a51e18d67a9b98698304982143d81e54eff Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 26 Mar 2025 17:23:33 -0400 Subject: [PATCH 08/15] Fixes build.gradle Signed-off-by: Darshit Chanpura --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 5a66ffcd61..2096f3372f 100644 --- a/build.gradle +++ b/build.gradle @@ -537,8 +537,8 @@ allprojects { integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}" integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}" integrationTestImplementation 'org.hamcrest:hamcrest:2.2' - integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}" - integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}" + integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${bouncycastle_version}" + integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${bouncycastle_version}" integrationTestImplementation('org.awaitility:awaitility:4.2.2') { exclude(group: 'org.hamcrest', module: 'hamcrest') } From a0d923dfd77a3db717cefd2df84c9eb219ad7e7c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 26 Mar 2025 17:28:34 -0400 Subject: [PATCH 09/15] Updates failureaccess dep Signed-off-by: Darshit Chanpura --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2096f3372f..a3f797e244 100644 --- a/build.gradle +++ b/build.gradle @@ -719,7 +719,7 @@ dependencies { implementation "com.nulab-inc:zxcvbn:1.9.0" - runtimeOnly 'com.google.guava:failureaccess:1.0.2' + runtimeOnly 'com.google.guava:failureaccess:1.0.3' runtimeOnly 'org.apache.commons:commons-text:1.13.0' runtimeOnly "org.glassfish.jaxb:jaxb-runtime:${jaxb_version}" runtimeOnly 'com.google.j2objc:j2objc-annotations:2.8' From dafd5c3b614b66314a62d15ac7743c0f21077f56 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 26 Mar 2025 17:44:39 -0400 Subject: [PATCH 10/15] Fixes build-artifacts workflow Signed-off-by: Darshit Chanpura --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdf34afd76..0ebb9b620a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -472,10 +472,10 @@ jobs: echo $test_qualifier # Publish SPI - ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version-all.jar - ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_no_snapshot-all.jar - ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false -Dbuild.version_qualifier=$test_qualifier && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier-all.jar - ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.version_qualifier=$test_qualifier && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier-SNAPSHOT-all.jar + ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version.jar + ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_no_snapshot.jar + ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.snapshot=false -Dbuild.version_qualifier=$test_qualifier && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier.jar + ./gradlew clean :opensearch-resource-sharing-spi:publishToMavenLocal -Dbuild.version_qualifier=$test_qualifier && test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version_only_number-$test_qualifier-SNAPSHOT.jar # Build artifacts @@ -498,10 +498,10 @@ jobs: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && \ test -s ./build/distributions/opensearch-security-$security_plugin_version.zip && \ test -s ./build/distributions/opensearch-security-$security_plugin_version.pom && \ - test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version-all.jar + test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version.jar ./gradlew clean publishShadowPublicationToMavenLocal && \ - test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version-all.jar + test -s ./spi/build/libs/opensearch-resource-sharing-spi-$security_plugin_version.jar - name: List files in build directory on failure if: failure() From 5738e11268c0a09751dbf69fdfb08e8f5306f68c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 26 Mar 2025 18:51:30 -0400 Subject: [PATCH 11/15] Removes recipient type registry logic Signed-off-by: Darshit Chanpura --- .../spi/resources/sharing/Recipient.java | 14 ++++ .../spi/resources/sharing/RecipientType.java | 24 ------- .../sharing/RecipientTypeRegistry.java | 39 ----------- .../sharing/SharedWithActionGroup.java | 23 +++---- .../resources/RecipientTypeRegistryTests.java | 43 ------------ .../spi/resources/ShareWithTests.java | 66 +++++-------------- 6 files changed, 40 insertions(+), 169 deletions(-) delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientType.java delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientTypeRegistry.java delete mode 100644 spi/src/test/java/org/opensearch/security/spi/resources/RecipientTypeRegistryTests.java diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Recipient.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Recipient.java index 77215071de..95d10107cf 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Recipient.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/Recipient.java @@ -28,4 +28,18 @@ public enum Recipient { public String getName() { return name; } + + public static Recipient fromValue(String name) { + for (Recipient recipient : Recipient.values()) { + if (recipient.name.equals(name)) { + return recipient; + } + } + throw new IllegalArgumentException("No Recipient with value: " + name); + } + + @Override + public String toString() { + return name; + } } diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientType.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientType.java deleted file mode 100644 index d3b916abc2..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientType.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.security.spi.resources.sharing; - -/** - * This class determines a type of recipient a resource can be shared with. - * An example type would be a user or a role. - * This class is used to determine the type of recipient a resource can be shared with. - * - * @opensearch.experimental - */ -public record RecipientType(String type) { - - @Override - public String toString() { - return type; - } -} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientTypeRegistry.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientTypeRegistry.java deleted file mode 100644 index a1bdb89089..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/RecipientTypeRegistry.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.security.spi.resources.sharing; - -import java.util.HashMap; -import java.util.Map; - -/** - * This class determines a collection of recipient types a resource can be shared with. - * Allows addition of other recipient types in the future. - * - * @opensearch.experimental - */ -public final class RecipientTypeRegistry { - // TODO: Check what size should this be. A cap should be added to avoid infinite addition of objects - private static final Integer REGISTRY_MAX_SIZE = 20; - private static final Map REGISTRY = new HashMap<>(10); - - public static void registerRecipientType(String key, RecipientType recipientType) { - if (REGISTRY.size() == REGISTRY_MAX_SIZE) { - throw new IllegalArgumentException("RecipientTypeRegistry is full. Cannot register more recipient types."); - } - REGISTRY.put(key, recipientType); - } - - public static RecipientType fromValue(String value) { - RecipientType type = REGISTRY.get(value); - if (type == null) { - throw new IllegalArgumentException("Unknown RecipientType: " + value + ". Must be 1 of these: " + REGISTRY.values()); - } - return type; - } -} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithActionGroup.java b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithActionGroup.java index 5d06d17de7..1c3800782d 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithActionGroup.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/sharing/SharedWithActionGroup.java @@ -100,9 +100,9 @@ public void writeTo(StreamOutput out) throws IOException { */ public static class ActionGroupRecipients implements ToXContentFragment, NamedWriteable { - private final Map> recipients; + private final Map> recipients; - public ActionGroupRecipients(Map> recipients) { + public ActionGroupRecipients(Map> recipients) { if (recipients == null) { throw new IllegalArgumentException("Recipients map cannot be null"); } @@ -110,13 +110,10 @@ public ActionGroupRecipients(Map> recipients) { } public ActionGroupRecipients(StreamInput in) throws IOException { - this.recipients = in.readMap( - key -> RecipientTypeRegistry.fromValue(key.readString()), - input -> input.readSet(StreamInput::readString) - ); + this.recipients = in.readMap(key -> key.readEnum(Recipient.class), input -> input.readSet(StreamInput::readString)); } - public Map> getRecipients() { + public Map> getRecipients() { return recipients; } @@ -126,20 +123,20 @@ public String getWriteableName() { } public static ActionGroupRecipients fromXContent(XContentParser parser) throws IOException { - Map> recipients = new HashMap<>(); + Map> recipients = new HashMap<>(); XContentParser.Token token; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { if (token == XContentParser.Token.FIELD_NAME) { String fieldName = parser.currentName(); - RecipientType recipientType = RecipientTypeRegistry.fromValue(fieldName); + Recipient recipient = Recipient.fromValue(fieldName); parser.nextToken(); Set values = new HashSet<>(); while (parser.nextToken() != XContentParser.Token.END_ARRAY) { values.add(parser.text()); } - recipients.put(recipientType, values); + recipients.put(recipient, values); } } @@ -150,7 +147,7 @@ public static ActionGroupRecipients fromXContent(XContentParser parser) throws I public void writeTo(StreamOutput out) throws IOException { out.writeMap( recipients, - (streamOutput, recipientType) -> streamOutput.writeString(recipientType.type()), + StreamOutput::writeEnum, (streamOutput, strings) -> streamOutput.writeCollection(strings, StreamOutput::writeString) ); } @@ -160,8 +157,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (recipients.isEmpty()) { return builder; } - for (Map.Entry> entry : recipients.entrySet()) { - builder.array(entry.getKey().type(), entry.getValue().toArray()); + for (Map.Entry> entry : recipients.entrySet()) { + builder.array(entry.getKey().getName(), entry.getValue().toArray()); } return builder; } diff --git a/spi/src/test/java/org/opensearch/security/spi/resources/RecipientTypeRegistryTests.java b/spi/src/test/java/org/opensearch/security/spi/resources/RecipientTypeRegistryTests.java deleted file mode 100644 index 8b0bfa3297..0000000000 --- a/spi/src/test/java/org/opensearch/security/spi/resources/RecipientTypeRegistryTests.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -package org.opensearch.security.spi.resources; - -import org.hamcrest.MatcherAssert; -import org.junit.Test; - -import org.opensearch.security.spi.resources.sharing.RecipientType; -import org.opensearch.security.spi.resources.sharing.RecipientTypeRegistry; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThrows; - -/** - * Tests for {@link RecipientTypeRegistry}. - * - * @opensearch.experimental - */ -public class RecipientTypeRegistryTests { - - @Test - public void testFromValue() { - RecipientTypeRegistry.registerRecipientType("ble1", new RecipientType("ble1")); - RecipientTypeRegistry.registerRecipientType("ble2", new RecipientType("ble2")); - - // Valid Value - RecipientType type = RecipientTypeRegistry.fromValue("ble1"); - MatcherAssert.assertThat(type, notNullValue()); - MatcherAssert.assertThat(type.type(), is(equalTo("ble1"))); - - // Invalid Value - IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> RecipientTypeRegistry.fromValue("bleble")); - MatcherAssert.assertThat("Unknown RecipientType: bleble. Must be 1 of these: [ble1, ble2]", is(equalTo(exception.getMessage()))); - } -} diff --git a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java index c2c271dd24..034bff2175 100644 --- a/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java +++ b/spi/src/test/java/org/opensearch/security/spi/resources/ShareWithTests.java @@ -15,7 +15,6 @@ import java.util.Set; import org.hamcrest.MatcherAssert; -import org.junit.Before; import org.junit.Test; import org.opensearch.common.xcontent.XContentFactory; @@ -26,8 +25,7 @@ import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.security.spi.resources.sharing.RecipientType; -import org.opensearch.security.spi.resources.sharing.RecipientTypeRegistry; +import org.opensearch.security.spi.resources.sharing.Recipient; import org.opensearch.security.spi.resources.sharing.ShareWith; import org.opensearch.security.spi.resources.sharing.SharedWithActionGroup; @@ -53,11 +51,6 @@ */ public class ShareWithTests { - @Before - public void setupResourceRecipientTypes() { - initializeRecipientTypes(); - } - @Test public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOException { String json = "{\"read_only\": {\"users\": [\"user1\"], \"roles\": [], \"backend_roles\": []}}"; @@ -77,14 +70,11 @@ public void testFromXContentWhenCurrentTokenIsNotStartObject() throws IOExceptio SharedWithActionGroup.ActionGroupRecipients actionGroupRecipients = actionGroup.getSharedWithPerActionGroup(); MatcherAssert.assertThat(actionGroupRecipients, notNullValue()); - Map> recipients = actionGroupRecipients.getRecipients(); - MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), is(1)); - MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())), contains("user1")); - MatcherAssert.assertThat(recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), is(0)); - MatcherAssert.assertThat( - recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), - is(0) - ); + Map> recipients = actionGroupRecipients.getRecipients(); + MatcherAssert.assertThat(recipients.get(Recipient.USERS).size(), is(1)); + MatcherAssert.assertThat(recipients.get(Recipient.USERS), contains("user1")); + MatcherAssert.assertThat(recipients.get(Recipient.ROLES).size(), is(0)); + MatcherAssert.assertThat(recipients.get(Recipient.BACKEND_ROLES).size(), is(0)); } @Test @@ -128,33 +118,15 @@ public void testFromXContentWithStartObject() throws IOException { for (SharedWithActionGroup actionGroup : actionGroups) { SharedWithActionGroup.ActionGroupRecipients perScope = actionGroup.getSharedWithPerActionGroup(); - Map> recipients = perScope.getRecipients(); + Map> recipients = perScope.getRecipients(); if (actionGroup.getActionGroup().equals(ResourceAccessActionGroups.PLACE_HOLDER)) { - MatcherAssert.assertThat( - recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), - is(2) - ); - MatcherAssert.assertThat( - recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), - is(1) - ); - MatcherAssert.assertThat( - recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), - is(1) - ); + MatcherAssert.assertThat(recipients.get(Recipient.USERS).size(), is(2)); + MatcherAssert.assertThat(recipients.get(Recipient.ROLES).size(), is(1)); + MatcherAssert.assertThat(recipients.get(Recipient.BACKEND_ROLES).size(), is(1)); } else if (actionGroup.getActionGroup().equals("random-action-group")) { - MatcherAssert.assertThat( - recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.USERS.getName())).size(), - is(1) - ); - MatcherAssert.assertThat( - recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.ROLES.getName())).size(), - is(1) - ); - MatcherAssert.assertThat( - recipients.get(RecipientTypeRegistry.fromValue(DefaultRecipientType.BACKEND_ROLES.getName())).size(), - is(1) - ); + MatcherAssert.assertThat(recipients.get(Recipient.USERS).size(), is(1)); + MatcherAssert.assertThat(recipients.get(Recipient.ROLES).size(), is(1)); + MatcherAssert.assertThat(recipients.get(Recipient.BACKEND_ROLES).size(), is(1)); } } } @@ -175,7 +147,7 @@ public void testFromXContentWithUnexpectedEndOfInput() throws IOException { public void testToXContentBuildsCorrectly() throws IOException { SharedWithActionGroup actionGroup = new SharedWithActionGroup( "actionGroup1", - new SharedWithActionGroup.ActionGroupRecipients(Map.of(new RecipientType("users"), Set.of("bleh"))) + new SharedWithActionGroup.ActionGroupRecipients(Map.of(Recipient.USERS, Set.of("bleh"))) ); Set actionGroups = new HashSet<>(); @@ -260,22 +232,16 @@ public void test_writeSharedWithScopesToStream() throws IOException { verify(mockStreamOutput, times(1)).writeCollection(eq(sharedWithActionGroups)); } - - private void initializeRecipientTypes() { - RecipientTypeRegistry.registerRecipientType("users", new RecipientType("users")); - RecipientTypeRegistry.registerRecipientType("roles", new RecipientType("roles")); - RecipientTypeRegistry.registerRecipientType("backend_roles", new RecipientType("backend_roles")); - } } -enum DefaultRecipientType { +enum DefaultRecipient { USERS("users"), ROLES("roles"), BACKEND_ROLES("backend_roles"); private final String name; - DefaultRecipientType(String name) { + DefaultRecipient(String name) { this.name = name; } From ef7bb47a95f3423e7437699743232933487bd04c Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Wed, 26 Mar 2025 19:35:23 -0400 Subject: [PATCH 12/15] Updates an exception class Signed-off-by: Darshit Chanpura --- .../ResourceSharingFeatureDisabledException.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java index 6a7dcc8291..eda13b1bc2 100644 --- a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java +++ b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java @@ -42,19 +42,6 @@ public ResourceSharingFeatureDisabledException(StreamInput in) throws IOExceptio @Override public RestStatus status() { - String message = getMessage(); - if (message.contains("not authorized")) { - return RestStatus.FORBIDDEN; - } else if (message.startsWith("No authenticated")) { - return RestStatus.UNAUTHORIZED; - } else if (message.contains("not found")) { - return RestStatus.NOT_FOUND; - } else if (message.contains("not a system index")) { - return RestStatus.BAD_REQUEST; - } else if (message.contains("is disabled")) { - return RestStatus.NOT_IMPLEMENTED; - } - - return RestStatus.INTERNAL_SERVER_ERROR; + return RestStatus.NOT_IMPLEMENTED; } } From a32b32ca00104767a925335e79a27314ce66ba35 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 27 Mar 2025 14:32:12 -0400 Subject: [PATCH 13/15] Removes custom resource-sharing exception classes Signed-off-by: Darshit Chanpura --- .../ResourceNonSystemIndexException.java | 46 ------------------ .../exceptions/ResourceNotFoundException.java | 46 ------------------ .../exceptions/ResourceSharingException.java | 47 ------------------- ...sourceSharingFeatureDisabledException.java | 47 ------------------- ...nauthenticatedResourceAccessException.java | 46 ------------------ .../UnauthorizedResourceAccessException.java | 46 ------------------ 6 files changed, 278 deletions(-) delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNonSystemIndexException.java delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNotFoundException.java delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthenticatedResourceAccessException.java delete mode 100644 spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthorizedResourceAccessException.java diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNonSystemIndexException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNonSystemIndexException.java deleted file mode 100644 index fb8d5af2c8..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNonSystemIndexException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.spi.resources.exceptions; - -import java.io.IOException; - -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.rest.RestStatus; - -/** - * This class represents an exception that is caused when a resource-index is registered as non-system index. - * It extends the ResourceSharingException class. - * - * @opensearch.experimental - */ -public final class ResourceNonSystemIndexException extends ResourceSharingException { - public ResourceNonSystemIndexException(Throwable cause) { - super(cause); - } - - public ResourceNonSystemIndexException(String msg, Object... args) { - super(msg, args); - } - - public ResourceNonSystemIndexException(String msg, Throwable cause, Object... args) { - super(msg, cause, args); - } - - public ResourceNonSystemIndexException(StreamInput in) throws IOException { - super(in); - } - - @Override - public RestStatus status() { - return RestStatus.BAD_REQUEST; - } -} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNotFoundException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNotFoundException.java deleted file mode 100644 index 67ffb8a411..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceNotFoundException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.spi.resources.exceptions; - -import java.io.IOException; - -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.rest.RestStatus; - -/** - * This class represents an exception that occurs when resource sharing feature is disabled or security plugin is disabled. - * It extends the ResourceSharingException class. - * - * @opensearch.experimental - */ -public final class ResourceNotFoundException extends ResourceSharingException { - public ResourceNotFoundException(Throwable cause) { - super(cause); - } - - public ResourceNotFoundException(String msg, Object... args) { - super(msg, args); - } - - public ResourceNotFoundException(String msg, Throwable cause, Object... args) { - super(msg, cause, args); - } - - public ResourceNotFoundException(StreamInput in) throws IOException { - super(in); - } - - @Override - public RestStatus status() { - return RestStatus.NOT_FOUND; - } -} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java deleted file mode 100644 index 46d583e1e2..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.spi.resources.exceptions; - -import java.io.IOException; - -import org.opensearch.OpenSearchException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.rest.RestStatus; - -/** - * This class represents a generic exception that occurs during resource sharing operations. - * It extends the OpenSearchException class. - * - * @opensearch.experimental - */ -public class ResourceSharingException extends OpenSearchException { - public ResourceSharingException(Throwable cause) { - super(cause); - } - - public ResourceSharingException(String msg, Object... args) { - super(msg, args); - } - - public ResourceSharingException(String msg, Throwable cause, Object... args) { - super(msg, cause, args); - } - - public ResourceSharingException(StreamInput in) throws IOException { - super(in); - } - - @Override - public RestStatus status() { - return RestStatus.INTERNAL_SERVER_ERROR; - } -} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java deleted file mode 100644 index eda13b1bc2..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/ResourceSharingFeatureDisabledException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.spi.resources.exceptions; - -import java.io.IOException; - -import org.opensearch.OpenSearchException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.rest.RestStatus; - -/** - * This class represents an exception that occurs during resource sharing operations. - * It extends the OpenSearchException class. - * - * @opensearch.experimental - */ -public final class ResourceSharingFeatureDisabledException extends OpenSearchException { - public ResourceSharingFeatureDisabledException(Throwable cause) { - super(cause); - } - - public ResourceSharingFeatureDisabledException(String msg, Object... args) { - super(msg, args); - } - - public ResourceSharingFeatureDisabledException(String msg, Throwable cause, Object... args) { - super(msg, cause, args); - } - - public ResourceSharingFeatureDisabledException(StreamInput in) throws IOException { - super(in); - } - - @Override - public RestStatus status() { - return RestStatus.NOT_IMPLEMENTED; - } -} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthenticatedResourceAccessException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthenticatedResourceAccessException.java deleted file mode 100644 index 634a910b21..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthenticatedResourceAccessException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.spi.resources.exceptions; - -import java.io.IOException; - -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.rest.RestStatus; - -/** - * This class represents an exception that occurs when an unauthenticated user tries to access a resource. - * It extends the ResourceSharingException class. - * - * @opensearch.experimental - */ -public final class UnauthenticatedResourceAccessException extends ResourceSharingException { - public UnauthenticatedResourceAccessException(Throwable cause) { - super(cause); - } - - public UnauthenticatedResourceAccessException(String msg, Object... args) { - super(msg, args); - } - - public UnauthenticatedResourceAccessException(String msg, Throwable cause, Object... args) { - super(msg, cause, args); - } - - public UnauthenticatedResourceAccessException(StreamInput in) throws IOException { - super(in); - } - - @Override - public RestStatus status() { - return RestStatus.UNAUTHORIZED; - } -} diff --git a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthorizedResourceAccessException.java b/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthorizedResourceAccessException.java deleted file mode 100644 index 888a0af7d7..0000000000 --- a/spi/src/main/java/org/opensearch/security/spi/resources/exceptions/UnauthorizedResourceAccessException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package org.opensearch.security.spi.resources.exceptions; - -import java.io.IOException; - -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.rest.RestStatus; - -/** - * This class represents an exception that occurs when an unauthorized user tries to access the resource. - * It extends the ResourceSharingException class. - * - * @opensearch.experimental - */ -public final class UnauthorizedResourceAccessException extends ResourceSharingException { - public UnauthorizedResourceAccessException(Throwable cause) { - super(cause); - } - - public UnauthorizedResourceAccessException(String msg, Object... args) { - super(msg, args); - } - - public UnauthorizedResourceAccessException(String msg, Throwable cause, Object... args) { - super(msg, cause, args); - } - - public UnauthorizedResourceAccessException(StreamInput in) throws IOException { - super(in); - } - - @Override - public RestStatus status() { - return RestStatus.FORBIDDEN; - } -} From 0798fea3c8e2bbb25d793224ec2e32d97f6a973e Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 28 Mar 2025 13:50:13 -0400 Subject: [PATCH 14/15] Updates spi README Signed-off-by: Darshit Chanpura --- spi/README.md | 43 ------------------------------------------- 1 file changed, 43 deletions(-) diff --git a/spi/README.md b/spi/README.md index 9c1bdf5a7f..5bd0dc44b1 100644 --- a/spi/README.md +++ b/spi/README.md @@ -112,49 +112,6 @@ Ensure that your **plugin declaration class** implements `ResourceSharingExtensi --- -### **6. Create a Client Accessor** -A **singleton accessor** should be created to manage the `ResourceSharingNodeClient`. -Example: -```java -public class ResourceSharingClientAccessor { - private static ResourceSharingNodeClient INSTANCE; - - private ResourceSharingClientAccessor() {} - - public static ResourceSharingNodeClient getResourceSharingClient(NodeClient nodeClient, Settings settings) { - if (INSTANCE == null) { - INSTANCE = new ResourceSharingNodeClient(nodeClient, settings); - } - return INSTANCE; - } -} -``` - ---- - -### **7. Utilize `ResourceSharingNodeClient` for Access Control** -Use the **client API methods** to manage resource sharing. - -#### **Example: Verifying Resource Access** -```java -ResourceSharingClient resourceSharingClient = ResourceSharingClientAccessor.getResourceSharingClient(nodeClient, settings); -resourceSharingClient.verifyResourceAccess( - "resource-123", - "resource_index", - ActionListener.wrap(isAuthorized -> { - if (isAuthorized) { - System.out.println("User has access to the resource."); - } else { - System.out.println("Access denied."); - } - }, e -> { - System.err.println("Failed to verify access: " + e.getMessage()); - }) -); -``` - ---- - ## **License** This project is licensed under the **Apache 2.0 License**. From c9e24858a4286b09356149ef3ba709ca1f285dce Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Fri, 28 Mar 2025 13:51:32 -0400 Subject: [PATCH 15/15] Updates forced dependencies Signed-off-by: Darshit Chanpura --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index a3f797e244..d6c8bdd058 100644 --- a/build.gradle +++ b/build.gradle @@ -503,7 +503,6 @@ configurations { force "org.mockito:mockito-core:5.16.1" force "net.bytebuddy:byte-buddy:1.15.11" force "org.ow2.asm:asm:9.7.1" - force "com.google.j2objc:j2objc-annotations:3.0.0" } } @@ -722,7 +721,7 @@ dependencies { runtimeOnly 'com.google.guava:failureaccess:1.0.3' runtimeOnly 'org.apache.commons:commons-text:1.13.0' runtimeOnly "org.glassfish.jaxb:jaxb-runtime:${jaxb_version}" - runtimeOnly 'com.google.j2objc:j2objc-annotations:2.8' + runtimeOnly 'com.google.j2objc:j2objc-annotations:3.0.0' compileOnly 'com.google.code.findbugs:jsr305:3.0.2' runtimeOnly 'org.lz4:lz4-java:1.8.0' runtimeOnly 'org.slf4j:slf4j-api:1.7.36'