mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-09-24 14:32:01 +00:00
Compare commits
18 Commits
23.6.0.5
...
fix/test_n
Author | SHA1 | Date | |
---|---|---|---|
|
4f675d13de | ||
|
3cd6b493d5 | ||
|
9309f2ab99 | ||
|
07fd5e8389 | ||
|
bdf32bffed | ||
|
f915e08b3d | ||
|
fab4d497e0 | ||
|
76c21e7102 | ||
|
a855fd7805 | ||
|
eef63bd242 | ||
|
d9d37470b5 | ||
|
817a89dd6c | ||
|
1e55a06512 | ||
|
ad52aac0a4 | ||
|
535651a682 | ||
|
9e14db2ee6 | ||
|
2652cdaa98 | ||
|
b595e41095 |
186
.github/workflows/ci.yml
vendored
186
.github/workflows/ci.yml
vendored
@@ -44,14 +44,14 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- id: changed-files
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/github-list-changes@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/github-list-changes@v7.1.0
|
||||
with:
|
||||
write-list-to-env: true
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/pre-commit@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/pre-commit@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Prepare maven cache and check compilation"
|
||||
@@ -69,12 +69,12 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/veracode@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/veracode@v7.1.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
srcclr-api-token: ${{ secrets.SRCCLR_API_TOKEN }}
|
||||
@@ -92,10 +92,10 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/github-download-file@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/github-download-file@v7.1.0
|
||||
with:
|
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
repository: "Alfresco/veracode-baseline-archive"
|
||||
@@ -142,10 +142,10 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[skip tests]') &&
|
||||
!contains(github.event.head_commit.message, '[force]')
|
||||
steps:
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/ya-pmd-scan@v4.3.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- uses: Alfresco/ya-pmd-scan@v4.1.0
|
||||
with:
|
||||
classpath-build-command: "mvn test-compile -ntp -Pags -pl \"-:alfresco-community-repo-docker\""
|
||||
|
||||
@@ -175,14 +175,14 @@ jobs:
|
||||
testAttributes: "-Dtest=AllMmtUnitTestSuite"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testModule }}
|
||||
@@ -213,7 +213,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -255,9 +255,9 @@ jobs:
|
||||
REQUIRES_INSTALLED_ARTIFACTS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -270,7 +270,7 @@ jobs:
|
||||
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }}
|
||||
@@ -301,7 +301,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -334,9 +334,9 @@ jobs:
|
||||
version: ['10.2.18', '10.4', '10.5']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: Run MariaDB ${{ matrix.version }} database
|
||||
@@ -345,7 +345,7 @@ jobs:
|
||||
MARIADB_VERSION: ${{ matrix.version }}
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.version }}
|
||||
@@ -376,7 +376,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -405,9 +405,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run MariaDB 10.6 database"
|
||||
@@ -416,7 +416,7 @@ jobs:
|
||||
MARIADB_VERSION: 10.6
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -447,7 +447,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -476,9 +476,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run MySQL 8 database"
|
||||
@@ -487,7 +487,7 @@ jobs:
|
||||
MYSQL_VERSION: 8
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -518,7 +518,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -546,9 +546,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run PostgreSQL 13.12 database"
|
||||
@@ -557,7 +557,7 @@ jobs:
|
||||
POSTGRES_VERSION: 13.12
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -588,7 +588,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -616,9 +616,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run PostgreSQL 14.9 database"
|
||||
@@ -627,7 +627,7 @@ jobs:
|
||||
POSTGRES_VERSION: 14.9
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -658,7 +658,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -686,9 +686,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run PostgreSQL 15.4 database"
|
||||
@@ -697,7 +697,7 @@ jobs:
|
||||
POSTGRES_VERSION: 15.4
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -728,7 +728,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -754,16 +754,16 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run ActiveMQ"
|
||||
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile activemq up -d
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -794,7 +794,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -854,9 +854,9 @@ jobs:
|
||||
mvn-options: '-Dencryption.ssl.keystore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.truststore'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Set transformers tag"
|
||||
@@ -879,7 +879,7 @@ jobs:
|
||||
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }} ${{ matrix.idp }}
|
||||
@@ -910,7 +910,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -968,9 +968,9 @@ jobs:
|
||||
REQUIRES_LOCAL_IMAGES: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -986,7 +986,7 @@ jobs:
|
||||
run: mvn install -pl :alfresco-community-repo-integration-test -am -DskipTests -Pall-tas-tests
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.test-name }}
|
||||
@@ -1024,7 +1024,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.tests.outcome }}
|
||||
@@ -1050,16 +1050,16 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run Postgres 15.4 database"
|
||||
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile postgres up -d
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -1090,7 +1090,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -1124,9 +1124,9 @@ jobs:
|
||||
REQUIRES_INSTALLED_ARTIFACTS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -1134,7 +1134,7 @@ jobs:
|
||||
bash ./scripts/ci/build.sh
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (PostgreSQL) ${{ matrix.test-name }}
|
||||
@@ -1170,9 +1170,9 @@ jobs:
|
||||
REQUIRES_INSTALLED_ARTIFACTS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -1180,7 +1180,7 @@ jobs:
|
||||
bash ./scripts/ci/build.sh
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (MySQL) ${{ matrix.test-name }}
|
||||
@@ -1212,9 +1212,9 @@ jobs:
|
||||
REQUIRES_LOCAL_IMAGES: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -1228,7 +1228,7 @@ jobs:
|
||||
mvn -B install -pl :alfresco-governance-services-automation-community-rest-api -am -Pags -Pall-tas-tests -DskipTests
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v7.1.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -1260,7 +1260,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v7.1.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -1302,9 +1302,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force]')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
|
16
.github/workflows/master_release.yml
vendored
16
.github/workflows/master_release.yml
vendored
@@ -34,12 +34,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v7.1.0
|
||||
with:
|
||||
username: ${{ env.GIT_USERNAME }}
|
||||
email: ${{ env.GIT_EMAIL }}
|
||||
@@ -63,12 +63,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v7.1.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v7.1.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v7.1.0
|
||||
with:
|
||||
username: ${{ env.GIT_USERNAME }}
|
||||
email: ${{ env.GIT_EMAIL }}
|
||||
|
@@ -1273,7 +1273,7 @@
|
||||
"filename": "repository/src/main/resources/alfresco/repository.properties",
|
||||
"hashed_secret": "84551ae5442affc9f1a2d3b4c86ae8b24860149d",
|
||||
"is_verified": false,
|
||||
"line_number": 771,
|
||||
"line_number": 770,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -1539,7 +1539,7 @@
|
||||
"filename": "repository/src/test/java/org/alfresco/repo/rendition2/AbstractRenditionIntegrationTest.java",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 130,
|
||||
"line_number": 127,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -1627,7 +1627,7 @@
|
||||
"filename": "repository/src/test/java/org/alfresco/repo/security/authentication/identityservice/SpringBasedIdentityServiceFacadeUnitTest.java",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 48,
|
||||
"line_number": 46,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -1888,5 +1888,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2025-05-15T21:47:13Z"
|
||||
}
|
||||
"generated_at": "2024-10-09T09:32:52Z"
|
||||
}
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-amps</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
@@ -26,6 +26,13 @@
|
||||
*/
|
||||
package org.alfresco.rest.rm.community.hold;
|
||||
|
||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
|
||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
|
||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
|
||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
|
||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
|
||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -37,25 +44,12 @@ import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
|
||||
|
||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_DESCRIPTION;
|
||||
import static org.alfresco.rest.rm.community.base.TestData.HOLD_REASON;
|
||||
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentAlias.FILE_PLAN_ALIAS;
|
||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
|
||||
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_READ_RECORDS;
|
||||
import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPrefix;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.dataprep.CMISUtil;
|
||||
import org.alfresco.dataprep.ContentActions;
|
||||
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
|
||||
@@ -77,6 +71,10 @@ import org.alfresco.utility.constants.UserRole;
|
||||
import org.alfresco.utility.model.FileModel;
|
||||
import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
* API tests for adding items to holds via the bulk process
|
||||
@@ -84,7 +82,7 @@ import org.alfresco.utility.model.UserModel;
|
||||
public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
{
|
||||
private static final String ACCESS_DENIED_ERROR_MESSAGE = "Access Denied. You do not have the appropriate " +
|
||||
"permissions to perform this operation.";
|
||||
"permissions to perform this operation.";
|
||||
private static final int NUMBER_OF_FILES = 5;
|
||||
private final List<FileModel> addedFiles = new ArrayList<>();
|
||||
private final List<UserModel> users = new ArrayList<>();
|
||||
@@ -104,9 +102,8 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
{
|
||||
STEP("Create a hold.");
|
||||
hold = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(),
|
||||
FILE_PLAN_ALIAS);
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold);
|
||||
|
||||
STEP("Create test files.");
|
||||
@@ -120,8 +117,8 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
for (int i = 0; i < NUMBER_OF_FILES; i++)
|
||||
{
|
||||
FileModel documentHeld = dataContent.usingAdmin()
|
||||
.usingResource(i % 2 == 0 ? folder1 : folder2)
|
||||
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
.usingResource(i % 2 == 0 ? folder1 : folder2)
|
||||
.createContent(CMISUtil.DocumentType.TEXT_PLAIN);
|
||||
addedFiles.add(documentHeld);
|
||||
}
|
||||
|
||||
@@ -131,37 +128,29 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
|
||||
STEP("Wait until all files are searchable.");
|
||||
await().atMost(30, TimeUnit.SECONDS)
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
|
||||
RestRequestQueryModel ancestorReq = getContentFromFolderAndAllSubfoldersQuery(rootFolder.getNodeRefWithoutVersion());
|
||||
SearchRequest ancestorSearchRequest = new SearchRequest();
|
||||
ancestorSearchRequest.setQuery(ancestorReq);
|
||||
|
||||
STEP("Wait until paths are indexed.");
|
||||
// to improve stability on CI - seems that sometimes during big load we need to wait longer for the condition
|
||||
await().atMost(120, TimeUnit.SECONDS)
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(null).search(ancestorSearchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
|
||||
holdBulkOperation = HoldBulkOperation.builder()
|
||||
.query(queryReq)
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
.query(queryReq)
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a site to a hold using the bulk API Then the content is added to the hold and the status of the bulk operation is DONE
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then the content is added to the hold and the status of the bulk operation is DONE
|
||||
*/
|
||||
@Test
|
||||
public void addContentFromTestSiteToHoldUsingBulkAPI()
|
||||
{
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
@@ -169,49 +158,50 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
|
||||
STEP("Wait until all files are added to the hold.");
|
||||
await().atMost(20, TimeUnit.SECONDS).until(
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold.getId()).getEntries().size() == NUMBER_OF_FILES);
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold.getId()).getEntries().size()
|
||||
== NUMBER_OF_FILES);
|
||||
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getChildren(hold.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId)
|
||||
.toList();
|
||||
.getChildren(hold.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId).toList();
|
||||
assertEquals(addedFiles.stream().map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
|
||||
STEP("Check the bulk status.");
|
||||
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 0, null, holdBulkOperation);
|
||||
|
||||
STEP("Check the bulk statuses.");
|
||||
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatuses(hold.getId());
|
||||
.getBulkStatuses(hold.getId());
|
||||
assertEquals(Arrays.asList(holdBulkStatus),
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a folder and all subfolders to a hold using the bulk API Then the content is added to the hold and the status of the bulk operation is DONE
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a folder and all subfolders to a hold using the bulk API
|
||||
* Then the content is added to the hold and the status of the bulk operation is DONE
|
||||
*/
|
||||
@Test
|
||||
public void addContentFromFolderAndAllSubfoldersToHoldUsingBulkAPI()
|
||||
{
|
||||
hold3 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(),
|
||||
FILE_PLAN_ALIAS);
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold3);
|
||||
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold3.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
UserRole.SiteCollaborator, hold3.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
// Get content from folder and all subfolders of the root folder
|
||||
HoldBulkOperation bulkOperation = HoldBulkOperation.builder()
|
||||
.query(getContentFromFolderAndAllSubfoldersQuery(rootFolder.getNodeRefWithoutVersion()))
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
.query(getContentFromFolderAndAllSubfoldersQuery(rootFolder.getNodeRefWithoutVersion()))
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(bulkOperation, hold3.getId());
|
||||
.startBulkProcess(bulkOperation, hold3.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
@@ -219,40 +209,43 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
|
||||
STEP("Wait until all files are added to the hold.");
|
||||
await().atMost(20, TimeUnit.SECONDS).until(
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold3.getId()).getEntries().size() == NUMBER_OF_FILES);
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold3.getId()).getEntries().size()
|
||||
== NUMBER_OF_FILES);
|
||||
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getChildren(hold3.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId)
|
||||
.toList();
|
||||
.getChildren(hold3.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId).toList();
|
||||
assertEquals(addedFiles.stream().map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
|
||||
STEP("Check the bulk status.");
|
||||
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold3.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
.getBulkStatus(hold3.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 0, null, bulkOperation);
|
||||
|
||||
STEP("Check the bulk statuses.");
|
||||
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatuses(hold3.getId());
|
||||
.getBulkStatuses(hold3.getId());
|
||||
assertEquals(List.of(holdBulkStatus),
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user without the add to hold capability When the user adds content from a site to a hold using the bulk API Then the user receives access denied error
|
||||
* Given a user without the add to hold capability
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then the user receives access denied error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessWithUserWithoutAddToHoldCapability()
|
||||
{
|
||||
UserModel userWithoutAddToHoldCapability = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator,
|
||||
hold.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
|
||||
UserRole
|
||||
.SiteCollaborator,
|
||||
hold.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
|
||||
users.add(userWithoutAddToHoldCapability);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
getRestAPIFactory().getHoldsAPI(userWithoutAddToHoldCapability)
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response status code and the error message.");
|
||||
assertStatusCode(FORBIDDEN);
|
||||
@@ -260,19 +253,21 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user without the filing permission on a hold When the user adds content from a site to a hold using the bulk API Then the user receives access denied error
|
||||
* Given a user without the filing permission on a hold
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then the user receives access denied error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessWithUserWithoutFilingPermissionOnAHold()
|
||||
{
|
||||
// User without filing permission on a hold
|
||||
UserModel userWithoutPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS);
|
||||
UserRole.SiteCollaborator, hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_READ_RECORDS);
|
||||
users.add(userWithoutPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
getRestAPIFactory().getHoldsAPI(userWithoutPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response status code and the error message.");
|
||||
assertStatusCode(FORBIDDEN);
|
||||
@@ -281,63 +276,68 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user without the write permission on all the content When the user adds content from a site to a hold using the bulk API Then all processed items are marked as errors and the last error message contains access denied error
|
||||
* Given a user without the write permission on all the content
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then all processed items are marked as errors and the last error message contains access denied error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessWithUserWithoutWritePermissionOnTheContent()
|
||||
{
|
||||
// User without write permission on the content
|
||||
UserModel userWithoutPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(
|
||||
testSite, UserRole.SiteConsumer,
|
||||
hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
testSite, UserRole.SiteConsumer,
|
||||
hold.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userWithoutPermission);
|
||||
|
||||
// Wait until permissions are reverted
|
||||
SearchRequest searchRequest = new SearchRequest();
|
||||
searchRequest.setQuery(holdBulkOperation.getQuery());
|
||||
await().atMost(30, TimeUnit.SECONDS)
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(userWithoutPermission).search(searchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(userWithoutPermission).search(searchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(
|
||||
userWithoutPermission).startBulkProcess(holdBulkOperation, hold.getId());
|
||||
userWithoutPermission).startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response.");
|
||||
assertStatusCode(ACCEPTED);
|
||||
|
||||
await().atMost(20, TimeUnit.SECONDS).until(() -> Objects.equals(getRestAPIFactory().getHoldsAPI(userWithoutPermission)
|
||||
await().atMost(20, TimeUnit.SECONDS).until(() ->
|
||||
Objects.equals(getRestAPIFactory().getHoldsAPI(userWithoutPermission)
|
||||
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId()).getStatus(), "DONE"));
|
||||
|
||||
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userWithoutPermission)
|
||||
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
.getBulkStatus(hold.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, NUMBER_OF_FILES, ACCESS_DENIED_ERROR_MESSAGE,
|
||||
holdBulkOperation);
|
||||
holdBulkOperation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user without the write permission on one file When the user adds content from a site to a hold using the bulk API Then all processed items are added to the hold except the one that the user does not have write permission And the status of the bulk operation is DONE, contains the error message and the number of errors is 1
|
||||
* Given a user without the write permission on one file
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then all processed items are added to the hold except the one that the user does not have write permission
|
||||
* And the status of the bulk operation is DONE, contains the error message and the number of errors is 1
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessWithUserWithoutWritePermissionOnOneFile()
|
||||
{
|
||||
hold2 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(),
|
||||
FILE_PLAN_ALIAS);
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold2);
|
||||
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold2.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
UserRole.SiteCollaborator, hold2.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
contentActions.setPermissionForUser(getAdminUser().getUsername(), getAdminUser().getPassword(),
|
||||
testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
|
||||
UserRole.SiteConsumer.getRoleId(), false);
|
||||
testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
|
||||
UserRole.SiteConsumer.getRoleId(), false);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold2.getId());
|
||||
.startBulkProcess(holdBulkOperation, hold2.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
@@ -345,50 +345,56 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
|
||||
STEP("Wait until all files are added to the hold.");
|
||||
await().atMost(30, TimeUnit.SECONDS).until(
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold2.getId()).getEntries().size() == NUMBER_OF_FILES - 1);
|
||||
() -> getRestAPIFactory().getHoldsAPI(getAdminUser()).getChildren(hold2.getId()).getEntries().size()
|
||||
== NUMBER_OF_FILES - 1);
|
||||
await().atMost(30, TimeUnit.SECONDS).until(
|
||||
() -> getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId()).getProcessedItems() == NUMBER_OF_FILES);
|
||||
() -> getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId()).getProcessedItems()
|
||||
== NUMBER_OF_FILES);
|
||||
List<String> holdChildrenNodeRefs = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getChildren(hold2.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId)
|
||||
.toList();
|
||||
.getChildren(hold2.getId()).getEntries().stream().map(HoldChildEntry::getEntry).map(
|
||||
HoldChild::getId).toList();
|
||||
assertEquals(addedFiles.stream().skip(1).map(FileModel::getNodeRefWithoutVersion).sorted().toList(),
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
holdChildrenNodeRefs.stream().sorted().toList());
|
||||
|
||||
STEP("Check the bulk status.");
|
||||
HoldBulkStatus holdBulkStatus = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
.getBulkStatus(hold2.getId(), bulkOperationEntry.getBulkStatusId());
|
||||
assertBulkProcessStatus(holdBulkStatus, NUMBER_OF_FILES, 1, ACCESS_DENIED_ERROR_MESSAGE, holdBulkOperation);
|
||||
|
||||
STEP("Check the bulk statuses.");
|
||||
HoldBulkStatusCollection holdBulkStatusCollection = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.getBulkStatuses(hold2.getId());
|
||||
.getBulkStatuses(hold2.getId());
|
||||
assertEquals(List.of(holdBulkStatus),
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
holdBulkStatusCollection.getEntries().stream().map(HoldBulkStatusEntry::getEntry).toList());
|
||||
|
||||
// Revert the permissions
|
||||
contentActions.setPermissionForUser(getAdminUser().getUsername(), getAdminUser().getPassword(),
|
||||
testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
|
||||
UserRole.SiteCollaborator.getRoleId(), true);
|
||||
testSite.getId(), addedFiles.get(0).getName(), userAddHoldPermission.getUsername(),
|
||||
UserRole.SiteCollaborator.getRoleId(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an unauthenticated user When the user adds content from a site to a hold using the bulk API Then the user receives unauthorized error
|
||||
* Given an unauthenticated user
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* Then the user receives unauthorized error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessAsUnauthenticatedUser()
|
||||
{
|
||||
STEP("Start bulk process as unauthenticated user");
|
||||
getRestAPIFactory().getHoldsAPI(new UserModel(getAdminUser().getUsername(), "wrongPassword"))
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
.startBulkProcess(holdBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
assertStatusCode(UNAUTHORIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a site to a hold using the bulk API And the hold does not exist Then the user receives not found error
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And the hold does not exist
|
||||
* Then the user receives not found error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessForNonExistentHold()
|
||||
@@ -401,7 +407,10 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a site to a hold using the bulk API and the bulk operation is invalid Then the user receives bad request error
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* and the bulk operation is invalid
|
||||
* Then the user receives bad request error
|
||||
*/
|
||||
@Test
|
||||
public void testGetBulkStatusesForInvalidOperation()
|
||||
@@ -409,7 +418,7 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
STEP("Start bulk process for non existent hold");
|
||||
|
||||
HoldBulkOperation invalidHoldBulkOperation = HoldBulkOperation.builder().op(null)
|
||||
.query(holdBulkOperation.getQuery()).build();
|
||||
.query(holdBulkOperation.getQuery()).build();
|
||||
getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(invalidHoldBulkOperation, hold.getId());
|
||||
|
||||
STEP("Verify the response status code.");
|
||||
@@ -417,7 +426,10 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a site to a hold using the bulk API And the hold does not exist Then the user receives not found error
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And the hold does not exist
|
||||
* Then the user receives not found error
|
||||
*/
|
||||
@Test
|
||||
public void testGetBulkStatusForNonExistentHold()
|
||||
@@ -430,7 +442,10 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a site to a hold using the bulk API And the bulk status does not exist Then the user receives not found error
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And the bulk status does not exist
|
||||
* Then the user receives not found error
|
||||
*/
|
||||
@Test
|
||||
public void testGetBulkStatusForNonExistentBulkStatus()
|
||||
@@ -443,7 +458,10 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a site to a hold using the bulk API And the hold does not exist Then the user receives not found error
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And the hold does not exist
|
||||
* Then the user receives not found error
|
||||
*/
|
||||
@Test
|
||||
public void testGetBulkStatusesForNonExistentHold()
|
||||
@@ -456,7 +474,9 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from all sites to a hold using the bulk API to exceed the limit (30 items) Then the user receives bad request error
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from all sites to a hold using the bulk API to exceed the limit (30 items)
|
||||
* Then the user receives bad request error
|
||||
*/
|
||||
@Test
|
||||
public void testExceedingBulkOperationLimit()
|
||||
@@ -466,8 +486,8 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
queryReq.setLanguage("afts");
|
||||
|
||||
HoldBulkOperation exceedLimitOp = HoldBulkOperation.builder()
|
||||
.query(queryReq)
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
.query(queryReq)
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
|
||||
STEP("Start bulk process to exceed the limit");
|
||||
getRestAPIFactory().getHoldsAPI(getAdminUser()).startBulkProcess(exceedLimitOp, hold.getId());
|
||||
@@ -477,24 +497,26 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a site to a hold using the bulk API And then the user cancels the bulk operation Then the user receives OK status code
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And then the user cancels the bulk operation
|
||||
* Then the user receives OK status code
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessCancellationWithAllowedUser()
|
||||
{
|
||||
Hold hold4 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(),
|
||||
FILE_PLAN_ALIAS);
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold4);
|
||||
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold4.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
UserRole.SiteCollaborator, hold4.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold4.getId());
|
||||
.startBulkProcess(holdBulkOperation, hold4.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
@@ -502,44 +524,47 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
|
||||
STEP("Cancel the bulk operation.");
|
||||
getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.cancelBulkOperation(hold4.getId(), bulkOperationEntry.getBulkStatusId(), new BulkBodyCancel());
|
||||
.cancelBulkOperation(hold4.getId(), bulkOperationEntry.getBulkStatusId(), new BulkBodyCancel());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a user with the add to hold capability and hold filing permission When the user adds content from a site to a hold using the bulk API And a 2nd user without the add to hold capability cancels the bulk operation Then the 2nd user receives access denied error
|
||||
* Given a user with the add to hold capability and hold filing permission
|
||||
* When the user adds content from a site to a hold using the bulk API
|
||||
* And a 2nd user without the add to hold capability cancels the bulk operation
|
||||
* Then the 2nd user receives access denied error
|
||||
*/
|
||||
@Test
|
||||
public void testBulkProcessCancellationWithUserWithoutAddToHoldCapability()
|
||||
{
|
||||
Hold hold5 = getRestAPIFactory().getFilePlansAPI(getAdminUser()).createHold(
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(),
|
||||
FILE_PLAN_ALIAS);
|
||||
Hold.builder().name("HOLD" + generateTestPrefix(AddToHoldsV1Tests.class)).description(HOLD_DESCRIPTION)
|
||||
.reason(HOLD_REASON).build(), FILE_PLAN_ALIAS);
|
||||
holds.add(hold5);
|
||||
|
||||
UserModel userAddHoldPermission = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator, hold5.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
UserRole.SiteCollaborator, hold5.getId(), UserRoles.ROLE_RM_MANAGER, PERMISSION_FILING);
|
||||
users.add(userAddHoldPermission);
|
||||
|
||||
STEP("Add content from the site to the hold using the bulk API.");
|
||||
HoldBulkOperationEntry bulkOperationEntry = getRestAPIFactory().getHoldsAPI(userAddHoldPermission)
|
||||
.startBulkProcess(holdBulkOperation, hold5.getId());
|
||||
.startBulkProcess(holdBulkOperation, hold5.getId());
|
||||
|
||||
// Verify the status code
|
||||
assertStatusCode(ACCEPTED);
|
||||
assertEquals(NUMBER_OF_FILES, bulkOperationEntry.getTotalItems());
|
||||
|
||||
UserModel userWithoutAddToHoldCapability = roleService.createUserWithSiteRoleRMRoleAndPermission(testSite,
|
||||
UserRole.SiteCollaborator,
|
||||
hold5.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
|
||||
UserRole
|
||||
.SiteCollaborator,
|
||||
hold5.getId(), UserRoles.ROLE_RM_POWER_USER, PERMISSION_FILING);
|
||||
users.add(userWithoutAddToHoldCapability);
|
||||
|
||||
STEP("Cancel the bulk operation.");
|
||||
getRestAPIFactory().getHoldsAPI(userWithoutAddToHoldCapability)
|
||||
.cancelBulkOperation(hold5.getId(), bulkOperationEntry.getBulkStatusId(), new BulkBodyCancel());
|
||||
.cancelBulkOperation(hold5.getId(), bulkOperationEntry.getBulkStatusId(), new BulkBodyCancel());
|
||||
|
||||
STEP("Verify the response status code and the error message.");
|
||||
assertStatusCode(FORBIDDEN);
|
||||
@@ -547,7 +572,7 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
}
|
||||
|
||||
private void assertBulkProcessStatus(HoldBulkStatus holdBulkStatus, long expectedProcessedItems,
|
||||
int expectedErrorsCount, String expectedErrorMessage, HoldBulkOperation holdBulkOperation)
|
||||
int expectedErrorsCount, String expectedErrorMessage, HoldBulkOperation holdBulkOperation)
|
||||
{
|
||||
assertEquals("DONE", holdBulkStatus.getStatus());
|
||||
assertEquals(expectedProcessedItems, holdBulkStatus.getTotalItems());
|
||||
|
@@ -23,7 +23,7 @@ Recorded content can be explicitly destroyed whilst maintaining the original nod
|
||||
* License: Alfresco Community
|
||||
* Issue Tracker Link: [JIRA RM](https://issues.alfresco.com/jira/projects/RM/summary)
|
||||
* Contribution Model: Alfresco Closed Source
|
||||
* Documentation: [docs.alfresco.com (Records Management)](https://support.hyland.com/r/Alfresco/Alfresco-Governance-Services-Community-Edition/23.4/Alfresco-Governance-Services-Community-Edition/Introduction)
|
||||
* Documentation: [docs.alfresco.com (Records Management)](http://docs.alfresco.com/rm2.4/concepts/welcome-rm.html)
|
||||
|
||||
***
|
||||
|
||||
|
@@ -21,18 +21,18 @@ RM is split into two main parts - a repository integration and a Share integrati
|
||||
* [Community License](../LICENSE.txt)
|
||||
* [Enterprise License](../../rm-enterprise/LICENSE.txt) (this file will only be present in clones of the Enterprise repository)
|
||||
* [Issue Tracker Link](https://issues.alfresco.com/jira/projects/RM)
|
||||
* [Community Documentation Link](https://support.hyland.com/r/Alfresco/Alfresco-Governance-Services-Community-Edition/23.4/Alfresco-Governance-Services-Community-Edition/Introduction)
|
||||
* [Enterprise Documentation Link](https://support.hyland.com/r/Alfresco/Alfresco-Governance-Services/23.4/Alfresco-Governance-Services/Introduction)
|
||||
* [Community Documentation Link](http://docs.alfresco.com/rm-community/concepts/welcome-rm.html)
|
||||
* [Enterprise Documentation Link](http://docs.alfresco.com/rm/concepts/welcome-rm.html)
|
||||
* [Contribution Model](../../CONTRIBUTING.md)
|
||||
|
||||
***
|
||||
|
||||
### Prerequisite Knowledge
|
||||
An understanding of Alfresco Content Services is assumed. The following pages from the [developer documentation](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services-Community-Edition/23.4/Alfresco-Content-Services-Community-Edition/Develop) give useful background information:
|
||||
An understanding of Alfresco Content Services is assumed. The following pages from the [developer documentation](http://docs.alfresco.com/5.2/concepts/dev-for-developers.html) give useful background information:
|
||||
|
||||
* [ACS Architecture](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Software-Architecture)
|
||||
* [Platform Extensions](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Extension-Points-Overview)
|
||||
* [Share Extensions](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Share-UI-Extension-Points)
|
||||
* [ACS Architecture](http://docs.alfresco.com/5.2/concepts/dev-arch-overview.html)
|
||||
* [Platform Extensions](http://docs.alfresco.com/5.2/concepts/dev-platform-extensions.html)
|
||||
* [Share Extensions](http://docs.alfresco.com/5.2/concepts/dev-extensions-share.html)
|
||||
|
||||
***
|
||||
|
||||
@@ -44,12 +44,12 @@ The RM Share module communicates with the repository module via REST APIs. Inter
|
||||
* A DAO layer responsible for CRUD operations against the database.
|
||||
|
||||
#### REST API
|
||||
The REST API endpoints fall into two main types - v0 (Webscripts) and v1. The [v0 API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/In-Process-Platform-Extension-Points/Web-Scripts) is older and not recommended for integrations. The [v1 API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/REST-API-Guide) is newer but isn't yet feature complete. If you are running RM locally then the GS API Explorer will be available at [this link](http://localhost:8080/gs-api-explorer/).
|
||||
The REST API endpoints fall into two main types - v0 (Webscripts) and v1. The [v0 API](http://docs.alfresco.com/5.2/references/dev-extension-points-webscripts.html) is older and not recommended for integrations. The [v1 API](http://docs.alfresco.com/5.1/pra/1/topics/pra-welcome-aara.html) is newer but isn't yet feature complete. If you are running RM locally then the GS API Explorer will be available at [this link](http://localhost:8080/gs-api-explorer/).
|
||||
|
||||
Internally the GS v1 REST API is built on the [Alfresco v1 REST API framework](https://community.alfresco.com/community/ecm/blog/2016/10/11/v1-rest-api-part-1-introduction). It aims to be consistent with this in terms of behaviour and naming.
|
||||
|
||||
#### Java Public API
|
||||
The Java service layer is fronted by a [Java Public API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Reference/Java-Foundation-API), which we will ensure backward compatible with previous releases. Before we remove any methods there will first be a release containing that method deprecated to allow third party integrations to migrate to a new method. The Java Public API also includes a set of POJO objects which are needed to communicate with the services. It is easy to identify classes that are part of the Java Public API as they are annotated `@AlfrescoPublicApi`.
|
||||
The Java service layer is fronted by a [Java Public API](http://docs.alfresco.com/5.2/concepts/java-public-api-list.html), which we will ensure backward compatible with previous releases. Before we remove any methods there will first be a release containing that method deprecated to allow third party integrations to migrate to a new method. The Java Public API also includes a set of POJO objects which are needed to communicate with the services. It is easy to identify classes that are part of the Java Public API as they are annotated `@AlfrescoPublicApi`.
|
||||
|
||||
Each Java service will have at least four beans defined for it:
|
||||
|
||||
@@ -61,7 +61,7 @@ Each Java service will have at least four beans defined for it:
|
||||
#### DAOs
|
||||
The DAOs are not part of the Java Public API, but handle CRUD operations against RM stored data. We have some custom queries to improve performance for particularly heavy operations.
|
||||
|
||||
We use standard Alfresco [data modelling](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/In-Process-Platform-Extension-Points/Content-Model-Extension-Point) to store RM metadata. We extend the [Alfresco patching mechanism](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/In-Process-Platform-Extension-Points/Patches) to provide community and enterprise schema upgrades.
|
||||
We use standard Alfresco [data modelling](http://docs.alfresco.com/5.2/references/dev-extension-points-content-model.html) to store RM metadata. We extend the [Alfresco patching mechanism](http://docs.alfresco.com/5.2/references/dev-extension-points-patch.html) to provide community and enterprise schema upgrades.
|
||||
|
||||
***
|
||||
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -34,7 +34,4 @@
|
||||
<!-- content cleanser -->
|
||||
<bean id="contentCleanser.522022M" class="org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser522022M"/>
|
||||
|
||||
<!-- content cleanser -->
|
||||
<bean id="contentCleanser.SevenPass" class="org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanserSevenPass"/>
|
||||
|
||||
</beans>
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Records Management Module
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.module.org_alfresco_module_rm.content.cleanser;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* DoD 5220-22M Seven Pass data cleansing implementation.
|
||||
*
|
||||
*/
|
||||
public class ContentCleanserSevenPass extends ContentCleanser522022M
|
||||
{
|
||||
/**
|
||||
* @see org.alfresco.module.org_alfresco_module_rm.content.cleanser.ContentCleanser#cleanse(java.io.File)
|
||||
*/
|
||||
@Override
|
||||
public void cleanse(File file)
|
||||
{
|
||||
super.cleanse(file);
|
||||
overwrite(file, overwriteZeros);
|
||||
overwrite(file, overwriteZeros);
|
||||
overwrite(file, overwriteOnes);
|
||||
overwrite(file, overwriteRandom);
|
||||
|
||||
}
|
||||
}
|
@@ -4,8 +4,8 @@
|
||||
|
||||
# Version label
|
||||
version.major=23
|
||||
version.minor=6
|
||||
version.revision=0
|
||||
version.minor=4
|
||||
version.revision=1
|
||||
version.label=
|
||||
|
||||
# Edition label
|
||||
|
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Records Management Module
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* -
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
* -
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
* -
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
* -
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.module.org_alfresco_module_rm.content.cleanser;
|
||||
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
|
||||
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
|
||||
/**
|
||||
* Eager content store cleaner unit test.
|
||||
*
|
||||
*/
|
||||
public class ContentCleanserSevenPassUnitTest extends BaseUnitTest
|
||||
{
|
||||
@InjectMocks
|
||||
@Spy
|
||||
private ContentCleanserSevenPass contentCleanserSevenPass = new ContentCleanserSevenPass()
|
||||
{
|
||||
/** dummy implementations */
|
||||
@Override
|
||||
protected void overwrite(File file, OverwriteOperation overwriteOperation)
|
||||
{
|
||||
// Intentionally left empty
|
||||
}
|
||||
};
|
||||
|
||||
@Mock
|
||||
private File mockedFile;
|
||||
|
||||
/**
|
||||
* Given that a file exists When I cleanse it Then the content is overwritten
|
||||
*/
|
||||
@Test
|
||||
public void cleanseFile()
|
||||
{
|
||||
when(mockedFile.exists()).thenReturn(true);
|
||||
when(mockedFile.canWrite()).thenReturn(true);
|
||||
contentCleanserSevenPass.cleanse(mockedFile);
|
||||
verify(contentCleanserSevenPass, times(2)).overwrite(mockedFile, contentCleanserSevenPass.overwriteOnes);
|
||||
verify(contentCleanserSevenPass, times(3)).overwrite(mockedFile, contentCleanserSevenPass.overwriteZeros);
|
||||
verify(contentCleanserSevenPass, times(2)).overwrite(mockedFile, contentCleanserSevenPass.overwriteRandom);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Given that the file does not exist When I cleanse it Then an exception is thrown
|
||||
*/
|
||||
@Test(expected = ContentIOException.class)
|
||||
public void fileDoesNotExist()
|
||||
{
|
||||
when(mockedFile.exists()).thenReturn(false);
|
||||
when(mockedFile.canWrite()).thenReturn(true);
|
||||
contentCleanserSevenPass.cleanse(mockedFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given that I can not write to the file When I cleanse it Then an exception is thrown
|
||||
*/
|
||||
@Test(expected = ContentIOException.class)
|
||||
public void cantWriteToFile()
|
||||
{
|
||||
when(mockedFile.exists()).thenReturn(true);
|
||||
when(mockedFile.canWrite()).thenReturn(false);
|
||||
contentCleanserSevenPass.cleanse(mockedFile);
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-amps</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
@@ -80,11 +80,6 @@ function runAction(p_params)
|
||||
{
|
||||
result.fileExist = true;
|
||||
}
|
||||
if (error.indexOf("FolderExistsException") != -1)
|
||||
{
|
||||
result.fileExist = true;
|
||||
result.type = "folder";
|
||||
}
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@@ -9,6 +9,6 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
</project>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
@@ -27,7 +27,7 @@
|
||||
|
||||
## Synopsis
|
||||
|
||||
**TAS**( **T**est **A**utomation **S**ystem)- **CMIS** is the project that handles the automated tests related only to CMIS API integrated with Alfresco One [Alfresco CMIS API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Reference/CMIS-API).
|
||||
**TAS**( **T**est **A**utomation **S**ystem)- **CMIS** is the project that handles the automated tests related only to CMIS API integrated with Alfresco One [Alfresco CMIS API](http://docs.alfresco.com/5.1/pra/1/topics/cmis-welcome.html).
|
||||
|
||||
It is based on Apache Maven, compatible with major IDEs and is using also Spring capabilities for dependency injection.
|
||||
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<organization>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
@@ -27,7 +27,7 @@ Back to [TAS Master Documentation](https://git.alfresco.com/tas/alfresco-tas-uti
|
||||
|
||||
## Synopsis
|
||||
|
||||
**TAS**( **T**est **A**utomation **S**ystem)- **RESTAPI** is the project that handles the automated tests related only to [Alfresco REST API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/REST-API-Guide).
|
||||
**TAS**( **T**est **A**utomation **S**ystem)- **RESTAPI** is the project that handles the automated tests related only to [Alfresco REST API](http://docs.alfresco.com/5.1/pra/1/topics/pra-welcome.html).
|
||||
|
||||
It is based on Apache Maven, compatible with major IDEs and is using also Spring capabilities for dependency injection.
|
||||
|
||||
@@ -271,7 +271,7 @@ restClient.onResponse().assertThat().body("entry.modifiedBy.firstName", org.hamc
|
||||
|
||||
### How to generate models or check coverage
|
||||
|
||||
There are some simple generators that could parse [Swagger YAML](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/REST-API-Guide/Things-to-Know-Before-You-Start/The-API-Explorer-is-Your-Source-of-Truth) files and provide some usefull information to you like:
|
||||
There are some simple generators that could parse [Swagger YAML](http://docs.alfresco.com/community/concepts/alfresco-sdk-tutorials-using-rest-api-explorer.html) files and provide some usefull information to you like:
|
||||
|
||||
a) Show on screen the actual coverage of TAS vs requests that exists in each YAML file - defined in pom.xml)
|
||||
|
||||
|
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -26,13 +26,6 @@
|
||||
package org.alfresco.rest.rules;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
|
||||
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.ERROR_MESSAGE_ACCESS_RESTRICTED;
|
||||
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.MAIL_ACTION;
|
||||
import static org.alfresco.rest.rules.RulesTestsUtils.CHECKIN_ACTION;
|
||||
@@ -52,6 +45,11 @@ import static org.alfresco.utility.model.FileModel.getRandomFileModel;
|
||||
import static org.alfresco.utility.model.FileType.TEXT_PLAIN;
|
||||
import static org.alfresco.utility.model.UserModel.getRandomUserModel;
|
||||
import static org.alfresco.utility.report.log.Step.STEP;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
@@ -63,13 +61,10 @@ import java.util.stream.IntStream;
|
||||
import jakarta.json.Json;
|
||||
import jakarta.json.JsonObject;
|
||||
|
||||
import org.apache.chemistry.opencmis.client.api.CmisObject;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.model.RestActionBodyExecTemplateModel;
|
||||
import org.alfresco.rest.model.RestActionConstraintModel;
|
||||
import org.alfresco.rest.model.RestCompositeConditionDefinitionModel;
|
||||
import org.alfresco.rest.model.RestPaginationModel;
|
||||
import org.alfresco.rest.model.RestRuleModel;
|
||||
import org.alfresco.rest.model.RestRuleModelsCollection;
|
||||
import org.alfresco.utility.constants.UserRole;
|
||||
@@ -79,6 +74,9 @@ import org.alfresco.utility.model.FolderModel;
|
||||
import org.alfresco.utility.model.SiteModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.apache.chemistry.opencmis.client.api.CmisObject;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
/**
|
||||
* Tests for POST /nodes/{nodeId}/rule-sets/{ruleSetId}/rules.
|
||||
@@ -103,13 +101,13 @@ public class CreateRulesTests extends RulesRestTest
|
||||
* <p>
|
||||
* Also check that the isShared field is not returned when not requested.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
|
||||
public void createRule()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithModifiedValues();
|
||||
|
||||
RestRuleModel rule = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
RestRuleModel expectedRuleModel = rulesUtils.createRuleModelWithModifiedValues();
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
@@ -119,7 +117,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check creating a rule in a non-existent folder returns an error. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleInNonExistentFolder()
|
||||
{
|
||||
STEP("Try to create a rule in non-existent folder.");
|
||||
@@ -136,7 +134,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check creating a rule in a non-existent rule set returns an error. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleInNonExistentRuleSet()
|
||||
{
|
||||
STEP("Try to create a rule in non-existent rule set.");
|
||||
@@ -150,7 +148,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Try to create a rule without a name and check the error. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleWithEmptyName()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModel("");
|
||||
@@ -162,7 +160,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check we can create two rules with the same name. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void duplicateRuleNameIsAcceptable()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModel("duplicateRuleName");
|
||||
@@ -177,7 +175,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that a user without permission to view the folder cannot create a rule in it. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void requireReadPermissionToCreateRule()
|
||||
{
|
||||
STEP("Create a user and use them to create a private site containing a folder");
|
||||
@@ -196,7 +194,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that a Collaborator cannot create a rule in a folder in a private site. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void siteCollaboratorCannotCreateRule()
|
||||
{
|
||||
testRolePermissionsWith(SiteCollaborator);
|
||||
@@ -206,7 +204,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that a Contributor cannot create a rule in a private folder. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void siteContributorCannotCreateRule()
|
||||
{
|
||||
testRolePermissionsWith(SiteContributor);
|
||||
@@ -216,7 +214,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that a Consumer cannot create a rule in a folder in a private site. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void siteConsumerCannotCreateRule()
|
||||
{
|
||||
testRolePermissionsWith(SiteConsumer);
|
||||
@@ -226,7 +224,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that a siteManager can create a rule in a folder in a private site. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void siteManagerCanCreateRule()
|
||||
{
|
||||
testRolePermissionsWith(SiteManager)
|
||||
@@ -236,7 +234,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check we can't create a rule under a document node. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void tryToCreateRuleUnderDocument()
|
||||
{
|
||||
STEP("Create a document.");
|
||||
@@ -252,7 +250,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check we can create several rules. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRules()
|
||||
{
|
||||
STEP("Create a list of rules in one POST request");
|
||||
@@ -260,18 +258,19 @@ public class CreateRulesTests extends RulesRestTest
|
||||
List<RestRuleModel> ruleModels = ruleNames.stream().map(rulesUtils::createRuleModel).collect(toList());
|
||||
|
||||
RestRuleModelsCollection rules = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createListOfRules(ruleModels);
|
||||
.createListOfRules(ruleModels);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
|
||||
assertEquals("Unexpected number of rules received in response.", ruleNames.size(), rules.getEntries().size());
|
||||
IntStream.range(0, ruleModels.size()).forEach(i -> rules.getEntries().get(i).onModel()
|
||||
.assertThat().field("id").isNotNull()
|
||||
.assertThat().field("name").is(ruleNames.get(i)));
|
||||
IntStream.range(0, ruleModels.size()).forEach(i ->
|
||||
rules.getEntries().get(i).onModel()
|
||||
.assertThat().field("id").isNotNull()
|
||||
.assertThat().field("name").is(ruleNames.get(i)));
|
||||
}
|
||||
|
||||
/** Check we can create over 100 rules and get them all back in response. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createOver100Rules()
|
||||
{
|
||||
STEP("Create a list of 120 rules in one POST request");
|
||||
@@ -288,9 +287,10 @@ public class CreateRulesTests extends RulesRestTest
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
|
||||
assertEquals("Unexpected number of rules received in response.", ruleCount, rules.getEntries().size());
|
||||
IntStream.range(0, ruleModels.size()).forEach(i -> rules.getEntries().get(i).onModel()
|
||||
.assertThat().field("id").isNotNull()
|
||||
.assertThat().field("name").is(ruleNamePrefix + (i + 1)));
|
||||
IntStream.range(0, ruleModels.size()).forEach(i ->
|
||||
rules.getEntries().get(i).onModel()
|
||||
.assertThat().field("id").isNotNull()
|
||||
.assertThat().field("name").is(ruleNamePrefix + (i + 1)));
|
||||
|
||||
rules.getPagination()
|
||||
.assertThat().field("count").is(ruleCount)
|
||||
@@ -302,7 +302,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Try to create several rules with an error in one of them. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRulesWithOneError()
|
||||
{
|
||||
STEP("Try to create a three rules but the middle one has an error.");
|
||||
@@ -319,55 +319,55 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check we can create a rule without description. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleWithoutDescription()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
UserModel admin = dataUser.getAdminUser();
|
||||
|
||||
RestRuleModel rule = restClient.authenticateUser(admin).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
rule.assertThat().field("id").isNotNull()
|
||||
.assertThat().field("name").is(RULE_NAME_DEFAULT)
|
||||
.assertThat().field("description").isNull();
|
||||
.assertThat().field("name").is(RULE_NAME_DEFAULT)
|
||||
.assertThat().field("description").isNull();
|
||||
}
|
||||
|
||||
/** Check we can create a rule without specifying triggers but with the default "inbound" value. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleWithoutTriggers()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
UserModel admin = dataUser.getAdminUser();
|
||||
|
||||
RestRuleModel rule = restClient.authenticateUser(admin).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
rule.assertThat().field("id").isNotNull()
|
||||
.assertThat().field("name").is(RULE_NAME_DEFAULT)
|
||||
.assertThat().field("triggers").is(List.of("inbound"));
|
||||
.assertThat().field("name").is(RULE_NAME_DEFAULT)
|
||||
.assertThat().field("triggers").is(List.of("inbound"));
|
||||
}
|
||||
|
||||
/** Check we can create a rule without error script. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleWithoutErrorScript()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
UserModel admin = dataUser.getAdminUser();
|
||||
|
||||
RestRuleModel rule = restClient.authenticateUser(admin).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
rule.assertThat().field("id").isNotNull()
|
||||
.assertThat().field("name").is(RULE_NAME_DEFAULT)
|
||||
.assertThat().field("errorScript").isNull();
|
||||
.assertThat().field("name").is(RULE_NAME_DEFAULT)
|
||||
.assertThat().field("errorScript").isNull();
|
||||
}
|
||||
|
||||
/** Check we can create a rule with irrelevant isShared flag, and it doesn't have impact to the process. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleWithSharedFlag()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
@@ -375,23 +375,23 @@ public class CreateRulesTests extends RulesRestTest
|
||||
UserModel admin = dataUser.getAdminUser();
|
||||
|
||||
RestRuleModel rule = restClient.authenticateUser(admin).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
rule.assertThat().field("id").isNotNull()
|
||||
.assertThat().field("name").is(RULE_NAME_DEFAULT)
|
||||
.assertThat().field("isShared").isNull();
|
||||
.assertThat().field("name").is(RULE_NAME_DEFAULT)
|
||||
.assertThat().field("isShared").isNull();
|
||||
}
|
||||
|
||||
/** Check we can create a rule. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
|
||||
public void createRuleAndIncludeFieldsInResponse()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModel("ruleName");
|
||||
|
||||
RestRuleModel rule = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.include("isShared")
|
||||
.createSingleRule(ruleModel);
|
||||
.include("isShared")
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
rule.assertThat().field("isShared").isNotNull();
|
||||
@@ -412,7 +412,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that the folder's owner can create rules, even if it is in a private site they aren't a member of. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void checkOwnerCanCreateRule()
|
||||
{
|
||||
STEP("Use admin to create a private site.");
|
||||
@@ -431,7 +431,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that an administrator can create a rule in a private site even if they aren't a member. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void checkAdminCanCreateRule()
|
||||
{
|
||||
STEP("Use a user to create a private site with a folder.");
|
||||
@@ -446,7 +446,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that a coordinator can create rules in folders outside sites. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void checkCoordinatorCanCreateRule()
|
||||
{
|
||||
STEP("Create a folder in the user's file space.");
|
||||
@@ -454,7 +454,11 @@ public class CreateRulesTests extends RulesRestTest
|
||||
|
||||
STEP("Create another user as a coordinator for this folder.");
|
||||
UserModel coordinator = dataUser.createRandomTestUser("Rules");
|
||||
/* Update folder node properties to add a coordinator { "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "coordinator.getUsername()", "name": "Coordinator", "accessStatus":"ALLOWED" } } } */
|
||||
/*
|
||||
Update folder node properties to add a coordinator
|
||||
{ "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "coordinator.getUsername()",
|
||||
"name": "Coordinator", "accessStatus":"ALLOWED" } } }
|
||||
*/
|
||||
String putBody = getAddPermissionsBody(coordinator.getUsername(), "Coordinator");
|
||||
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).updateNode(putBody);
|
||||
|
||||
@@ -466,7 +470,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that an editor cannot create rules in folders outside sites. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void checkEditorCannotCreateRule()
|
||||
{
|
||||
STEP("Create a folder in the user's file space.");
|
||||
@@ -474,7 +478,11 @@ public class CreateRulesTests extends RulesRestTest
|
||||
|
||||
STEP("Create another user as a editor for this folder.");
|
||||
UserModel editor = dataUser.createRandomTestUser();
|
||||
/* Update folder node properties to add an editor { "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "editor.getUsername()", "name": "Coordinator", "accessStatus":"ALLOWED" } } } */
|
||||
/*
|
||||
Update folder node properties to add an editor
|
||||
{ "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "editor.getUsername()",
|
||||
"name": "Coordinator", "accessStatus":"ALLOWED" } } }
|
||||
*/
|
||||
String putBody = getAddPermissionsBody(editor.getUsername(), "Editor");
|
||||
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).updateNode(putBody);
|
||||
|
||||
@@ -486,7 +494,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
}
|
||||
|
||||
/** Check that a collaborator cannot create rules in folders outside sites. */
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void checkCollaboratorCannotCreateRule()
|
||||
{
|
||||
STEP("Create a folder in the user's file space.");
|
||||
@@ -494,7 +502,11 @@ public class CreateRulesTests extends RulesRestTest
|
||||
|
||||
STEP("Create another user as a collaborator for this folder.");
|
||||
UserModel collaborator = dataUser.createRandomTestUser();
|
||||
/* Update folder node properties to add a collaborator { "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "collaborator.getUsername()", "name": "Coordinator", "accessStatus":"ALLOWED" } } } */
|
||||
/*
|
||||
Update folder node properties to add a collaborator
|
||||
{ "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "collaborator.getUsername()",
|
||||
"name": "Coordinator", "accessStatus":"ALLOWED" } } }
|
||||
*/
|
||||
String putBody = getAddPermissionsBody(collaborator.getUsername(), "Collaborator");
|
||||
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).updateNode(putBody);
|
||||
|
||||
@@ -560,10 +572,10 @@ public class CreateRulesTests extends RulesRestTest
|
||||
public void createRuleWithActions_userCannotUsePrivateAction()
|
||||
{
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(rulesUtils.createRuleWithPrivateAction());
|
||||
.createSingleRule(rulesUtils.createRuleWithPrivateAction());
|
||||
|
||||
restClient.assertStatusCodeIs(FORBIDDEN)
|
||||
.assertLastError().containsSummary(ERROR_MESSAGE_ACCESS_RESTRICTED);
|
||||
.assertLastError().containsSummary(ERROR_MESSAGE_ACCESS_RESTRICTED);
|
||||
}
|
||||
|
||||
/** Check that an administrator can create rules that use private actions. */
|
||||
@@ -571,7 +583,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
public void createRuleWithActions_adminCanUsePrivateAction()
|
||||
{
|
||||
restClient.authenticateUser(dataUser.getAdminUser()).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(rulesUtils.createRuleWithPrivateAction());
|
||||
.createSingleRule(rulesUtils.createRuleWithPrivateAction());
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
}
|
||||
@@ -644,7 +656,8 @@ public class CreateRulesTests extends RulesRestTest
|
||||
public void createRuleWithNotApplicableActionShouldFail()
|
||||
{
|
||||
final RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
final RestActionBodyExecTemplateModel invalidAction = rulesUtils.createCustomActionModel(RulesTestsUtils.DELETE_RENDITION_ACTION, Map.of("dummy-key", "dummy-value"));
|
||||
final RestActionBodyExecTemplateModel invalidAction =
|
||||
rulesUtils.createCustomActionModel(RulesTestsUtils.DELETE_RENDITION_ACTION, Map.of("dummy-key", "dummy-value"));
|
||||
ruleModel.setActions(List.of(invalidAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet().createSingleRule(ruleModel);
|
||||
@@ -660,7 +673,8 @@ public class CreateRulesTests extends RulesRestTest
|
||||
public void createRuleWithMissingActionParametersShouldFail()
|
||||
{
|
||||
final RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
final RestActionBodyExecTemplateModel invalidAction = rulesUtils.createCustomActionModel(RulesTestsUtils.COPY_ACTION, Collections.emptyMap());
|
||||
final RestActionBodyExecTemplateModel invalidAction =
|
||||
rulesUtils.createCustomActionModel(RulesTestsUtils.COPY_ACTION, Collections.emptyMap());
|
||||
ruleModel.setActions(List.of(invalidAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
@@ -722,7 +736,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
public void createRuleWithoutMandatoryActionParametersShouldFail()
|
||||
{
|
||||
final RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
final RestActionBodyExecTemplateModel invalidAction = rulesUtils.createCustomActionModel(COPY_ACTION, Map.of("deep-copy", false));
|
||||
final RestActionBodyExecTemplateModel invalidAction = rulesUtils.createCustomActionModel(COPY_ACTION, Map.of("deep-copy",false));
|
||||
ruleModel.setActions(List.of(invalidAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
@@ -735,7 +749,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
/**
|
||||
* Check we get error when attempting to create a rule that copies files to a non-existent folder.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleThatUsesNonExistentNode()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
@@ -744,16 +758,16 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setActions(List.of(invalidAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(NOT_FOUND);
|
||||
restClient.assertLastError().containsSummary("Destination folder having Id: non-existent-node no longer exists. Please update your rule definition.");
|
||||
restClient.assertLastError().containsSummary("The entity with id: non-existent-node was not found");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check we get error when attempting to create a rule that references a folder that the user does not have read permission for.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleThatUsesNodeWithoutReadPermission()
|
||||
{
|
||||
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
|
||||
@@ -765,7 +779,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setActions(List.of(invalidAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(NOT_FOUND);
|
||||
restClient.assertLastError().containsSummary("The entity with id: " + privateFolder.getNodeRef() + " was not found");
|
||||
@@ -774,7 +788,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
/**
|
||||
* Check we get error when attempting to create a rule that copies files to a folder that a user only has read permission for.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void createRuleThatWritesToNodeWithoutPermission()
|
||||
{
|
||||
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
|
||||
@@ -788,7 +802,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setActions(List.of(invalidAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(FORBIDDEN);
|
||||
restClient.assertLastError().containsSummary("No proper permissions for node: " + privateFolder.getNodeRef());
|
||||
@@ -814,6 +828,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
restClient.assertLastError().containsSummary("Node is not a folder " + fileModel.getNodeRef());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check we get error when attempting to create a rule with mail action defined with non-existing mail template.
|
||||
*/
|
||||
@@ -835,7 +850,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setActions(List.of(mailAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(BAD_REQUEST);
|
||||
restClient.assertLastError().containsSummary("Action parameter: template has invalid value (" + mailTemplate +
|
||||
@@ -845,7 +860,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
/**
|
||||
* Check the user can create a rule with a script.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void checkCanUseScriptInRule()
|
||||
{
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
@@ -854,7 +869,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setActions(List.of(scriptAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(CREATED);
|
||||
}
|
||||
@@ -862,7 +877,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
/**
|
||||
* Check the script has to be stored in the scripts directory in the data dictionary.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void checkCantUseNodeOutsideScriptsDirectory()
|
||||
{
|
||||
STEP("Copy script to location outside data dictionary.");
|
||||
@@ -883,16 +898,16 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setActions(List.of(scriptAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(BAD_REQUEST)
|
||||
.assertLastError().containsSummary("script-ref has invalid value");
|
||||
.assertLastError().containsSummary("script-ref has invalid value");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a real category needs to be supplied when linking to a category.
|
||||
*/
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
|
||||
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
|
||||
public void checkLinkToCategoryNeedsRealCategory()
|
||||
{
|
||||
STEP("Attempt to link to a category with a folder node, rather than a category node.");
|
||||
@@ -903,7 +918,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setActions(List.of(categoryAction));
|
||||
|
||||
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
restClient.assertStatusCodeIs(BAD_REQUEST);
|
||||
}
|
||||
@@ -918,7 +933,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setConditions(rulesUtils.createVariousConditions());
|
||||
|
||||
RestRuleModel rule = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
RestRuleModel expectedRuleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
expectedRuleModel.setConditions(rulesUtils.createVariousConditions());
|
||||
@@ -937,7 +952,7 @@ public class CreateRulesTests extends RulesRestTest
|
||||
ruleModel.setConditions(rulesUtils.createCompositeCondition(null));
|
||||
|
||||
RestRuleModel rule = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
|
||||
.createSingleRule(ruleModel);
|
||||
.createSingleRule(ruleModel);
|
||||
|
||||
RestRuleModel expectedRuleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
expectedRuleModel.setTriggers(List.of("inbound"));
|
||||
@@ -954,8 +969,10 @@ public class CreateRulesTests extends RulesRestTest
|
||||
STEP("Try to create a rule with non existing category in conditions.");
|
||||
String fakeCategoryId = "bdba5f9f-fake-id22-803b-349bcfd06fd1";
|
||||
RestCompositeConditionDefinitionModel conditions = rulesUtils.createCompositeCondition(List.of(
|
||||
rulesUtils.createCompositeCondition(!INVERTED, List.of(
|
||||
rulesUtils.createSimpleCondition("category", "equals", fakeCategoryId)))));
|
||||
rulesUtils.createCompositeCondition(!INVERTED, List.of(
|
||||
rulesUtils.createSimpleCondition("category", "equals", fakeCategoryId)
|
||||
))
|
||||
));
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
ruleModel.setConditions(conditions);
|
||||
|
||||
@@ -975,7 +992,9 @@ public class CreateRulesTests extends RulesRestTest
|
||||
final String comparator = "greaterthan";
|
||||
RestCompositeConditionDefinitionModel conditions = rulesUtils.createCompositeCondition(List.of(
|
||||
rulesUtils.createCompositeCondition(!INVERTED, List.of(
|
||||
rulesUtils.createSimpleCondition("size", comparator, "500")))));
|
||||
rulesUtils.createSimpleCondition("size", comparator, "500")
|
||||
))
|
||||
));
|
||||
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
|
||||
ruleModel.setConditions(conditions);
|
||||
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<!-- Resource defaultTransactionIsolation="-1" defaultAutoCommit="false" maxActive="100" initialSize="10" password="alfresco" username="alfresco" url="jdbc:mysql:///alfresco" driverClassName="org.gjt.mm.mysql.Driver" type="javax.sql.DataSource" auth="Container" name="jdbc/dataSource"/-->
|
||||
<Environment override="false" type="java.lang.Boolean" name="properties/startup.enable" description="A flag that globally enables or disables startup of the major Alfresco subsystems." value="true"/>
|
||||
<Environment override="false" type="java.lang.String" name="properties/dir.root" description="The filesystem directory below which content and index data is stored. Should be on a shared disk if this is a clustered installation."/>
|
||||
<Environment override="false" type="java.lang.String" name="properties/hibernate.dialect" description="The fully qualified name of a org.hibernate.dialect.Dialect subclass that allows Hibernate to generate SQL optimized for a particular relational database. Choose from org.hibernate.dialect.DerbyDialect, org.hibernate.dialect.MySQLInnoDBDialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9Dialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect, org.alfresco.repo.domain.hibernate.dialect.SQLServerDialect, org.hibernate.dialect.PostgreSQLDialect"/>
|
||||
<Environment override="false" type="java.lang.String" name="properties/hibernate.dialect" description="The fully qualified name of a org.hibernate.dialect.Dialect subclass that allows Hibernate to generate SQL optimized for a particular relational database. Choose from org.hibernate.dialect.DerbyDialect, org.hibernate.dialect.MySQLInnoDBDialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9Dialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect, org.hibernate.dialect.PostgreSQLDialect"/>
|
||||
<Environment override="false" type="java.lang.String" name="properties/hibernate.query.substitutions" description="Mapping from tokens in Hibernate queries to SQL tokens. For PostgreSQL, set this to "true TRUE, false FALSE"."/>
|
||||
<Environment override="false" type="java.lang.Boolean" name="properties/hibernate.jdbc.use_get_generated_keys" description="Enable use of JDBC3 PreparedStatement.getGeneratedKeys() to retrieve natively generated keys after insert. Requires JDBC3+ driver. Set to false if your driver has problems with the Hibernate identifier generators. By default, tries to determine the driver capabilities using connection metadata."/>
|
||||
<Environment override="false" type="java.lang.String" name="properties/hibernate.default_schema" description="Qualify unqualified table names with the given schema/tablespace in generated SQL. It may be necessary to set this when the target database has more than one schema."/>
|
||||
|
@@ -500,7 +500,7 @@
|
||||
org.hibernate.dialect.MySQLInnoDBDialect,
|
||||
org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9Dialect,
|
||||
org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect,
|
||||
org.alfresco.repo.domain.hibernate.dialect.SQLServerDialect, org.hibernate.dialect.PostgreSQLDialect</description>
|
||||
org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect, org.hibernate.dialect.PostgreSQLDialect</description>
|
||||
<env-entry-name>properties/hibernate.dialect</env-entry-name>
|
||||
<env-entry-type>java.lang.String</env-entry-type>
|
||||
<env-entry-value/> <!-- Empty value included for JBoss compatibility -->
|
||||
|
@@ -74,7 +74,7 @@ ModuleDetails shareServicesModule = moduleService.getModule("alfresco-share-serv
|
||||
<div class="index-list">
|
||||
<h4><%=descriptorService.getServerDescriptor().getEdition()%></h4>
|
||||
<p></p>
|
||||
<p><a href="https://support.hyland.com/p/alfresco">Online Documentation</a></p>
|
||||
<p><a href="http://docs.alfresco.com/">Online Documentation</a></p>
|
||||
<p></p>
|
||||
<%
|
||||
if (shareServicesModule != null && ModuleInstallState.INSTALLED.equals(shareServicesModule.getInstallState()))
|
||||
|
30
pom.xml
30
pom.xml
@@ -2,7 +2,7 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Alfresco Community Repo Parent</name>
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
|
||||
<properties>
|
||||
<acs.version.major>23</acs.version.major>
|
||||
<acs.version.minor>6</acs.version.minor>
|
||||
<acs.version.revision>0</acs.version.revision>
|
||||
<acs.version.minor>4</acs.version.minor>
|
||||
<acs.version.revision>2</acs.version.revision>
|
||||
<acs.version.label />
|
||||
<amp.min.version>${acs.version.major}.0.0</amp.min.version>
|
||||
|
||||
@@ -58,12 +58,12 @@
|
||||
|
||||
<dependency.aspectj.version>1.9.22.1</dependency.aspectj.version>
|
||||
<dependency.spring.version>6.1.14</dependency.spring.version>
|
||||
<dependency.spring-security.version>6.3.8</dependency.spring-security.version>
|
||||
<dependency.spring-security.version>6.3.4</dependency.spring-security.version>
|
||||
<dependency.antlr.version>3.5.3</dependency.antlr.version>
|
||||
<dependency.jackson.version>2.17.2</dependency.jackson.version>
|
||||
<dependency.cxf.version>4.1.0</dependency.cxf.version>
|
||||
<dependency.opencmis.version>1.0.0-jakarta-1</dependency.opencmis.version>
|
||||
<dependency.webscripts.version>10.2</dependency.webscripts.version>
|
||||
<dependency.webscripts.version>9.4</dependency.webscripts.version>
|
||||
<dependency.bouncycastle.version>1.78.1</dependency.bouncycastle.version>
|
||||
<dependency.mockito-core.version>5.14.1</dependency.mockito-core.version>
|
||||
<dependency.assertj.version>3.26.3</dependency.assertj.version>
|
||||
@@ -83,10 +83,10 @@
|
||||
<dependency.groovy.version>3.0.22</dependency.groovy.version>
|
||||
<dependency.tika.version>2.9.2</dependency.tika.version>
|
||||
<dependency.truezip.version>7.7.10</dependency.truezip.version>
|
||||
<dependency.poi.version>5.4.1</dependency.poi.version>
|
||||
<dependency.poi.version>5.3.0</dependency.poi.version>
|
||||
<dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version>
|
||||
<dependency.camel.version>4.10.2</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
|
||||
<dependency.netty.version>4.1.118.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
|
||||
<dependency.camel.version>4.6.0</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
|
||||
<dependency.netty.version>4.1.117.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
|
||||
<dependency.activemq.version>5.18.3</dependency.activemq.version>
|
||||
<dependency.apache-compress.version>1.27.1</dependency.apache-compress.version>
|
||||
<dependency.awaitility.version>4.2.2</dependency.awaitility.version>
|
||||
@@ -111,9 +111,9 @@
|
||||
<dependency.jakarta-ee-json-api.version>2.1.3</dependency.jakarta-ee-json-api.version>
|
||||
<dependency.jakarta-ee-json-impl.version>1.1.7</dependency.jakarta-ee-json-impl.version>
|
||||
<dependency.jakarta-json-path.version>2.9.0</dependency.jakarta-json-path.version>
|
||||
<dependency.json-smart.version>2.5.2</dependency.json-smart.version>
|
||||
<dependency.json-smart.version>2.5.1</dependency.json-smart.version>
|
||||
<alfresco.googledrive.version>4.1.0</alfresco.googledrive.version>
|
||||
<alfresco.aos-module.version>3.3.0</alfresco.aos-module.version>
|
||||
<alfresco.aos-module.version>3.2.0</alfresco.aos-module.version>
|
||||
<alfresco.api-explorer.version>23.4.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
|
||||
|
||||
<alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
|
||||
@@ -154,7 +154,7 @@
|
||||
<connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection>
|
||||
<developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection>
|
||||
<url>https://github.com/Alfresco/alfresco-community-repo</url>
|
||||
<tag>23.6.0.5</tag>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
<distributionManagement>
|
||||
@@ -409,7 +409,7 @@
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>1.11.0</version>
|
||||
<version>1.9.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
@@ -439,8 +439,8 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
|
||||
<version>2.0.0-M4</version>
|
||||
<artifactId>commons-fileupload2-jakarta</artifactId>
|
||||
<version>2.0.0-M1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
@@ -1186,4 +1186,4 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -34,7 +34,7 @@ import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
|
||||
import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator;
|
||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||
import org.alfresco.repo.web.auth.AuthenticationListener;
|
||||
import org.alfresco.repo.web.auth.TicketCredentials;
|
||||
@@ -55,7 +55,7 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Authenticator to provide Remote User based Header authentication dropping back to Basic Auth otherwise.
|
||||
* Authenticator to provide Remote User based Header authentication dropping back to Basic Auth otherwise.
|
||||
* Statelessly authenticating via a secure header now does not require a Session so can be used with
|
||||
* request-level load balancers which was not previously possible.
|
||||
* <p>
|
||||
@@ -73,11 +73,9 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
|
||||
protected RemoteUserMapper remoteUserMapper;
|
||||
protected AuthenticationComponent authenticationComponent;
|
||||
protected ExternalUserAuthenticator adminConsoleAuthenticator;
|
||||
protected ExternalUserAuthenticator webScriptsHomeAuthenticator;
|
||||
protected AdminConsoleAuthenticator adminConsoleAuthenticator;
|
||||
|
||||
private boolean alwaysAllowBasicAuthForAdminConsole = true;
|
||||
private boolean alwaysAllowBasicAuthForWebScriptsHome = true;
|
||||
List<String> adminConsoleScriptFamilies;
|
||||
long getRemoteUserTimeoutMilliseconds = GET_REMOTE_USER_TIMEOUT_MILLISECONDS_DEFAULT;
|
||||
|
||||
@@ -86,7 +84,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
{
|
||||
this.remoteUserMapper = remoteUserMapper;
|
||||
}
|
||||
|
||||
|
||||
public void setAuthenticationComponent(AuthenticationComponent authenticationComponent)
|
||||
{
|
||||
this.authenticationComponent = authenticationComponent;
|
||||
@@ -102,16 +100,6 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
this.alwaysAllowBasicAuthForAdminConsole = alwaysAllowBasicAuthForAdminConsole;
|
||||
}
|
||||
|
||||
public boolean isAlwaysAllowBasicAuthForWebScriptsHome()
|
||||
{
|
||||
return alwaysAllowBasicAuthForWebScriptsHome;
|
||||
}
|
||||
|
||||
public void setAlwaysAllowBasicAuthForWebScriptsHome(boolean alwaysAllowBasicAuthForWebScriptsHome)
|
||||
{
|
||||
this.alwaysAllowBasicAuthForWebScriptsHome = alwaysAllowBasicAuthForWebScriptsHome;
|
||||
}
|
||||
|
||||
public List<String> getAdminConsoleScriptFamilies()
|
||||
{
|
||||
return adminConsoleScriptFamilies;
|
||||
@@ -133,17 +121,11 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
}
|
||||
|
||||
public void setAdminConsoleAuthenticator(
|
||||
ExternalUserAuthenticator adminConsoleAuthenticator)
|
||||
AdminConsoleAuthenticator adminConsoleAuthenticator)
|
||||
{
|
||||
this.adminConsoleAuthenticator = adminConsoleAuthenticator;
|
||||
}
|
||||
|
||||
public void setWebScriptsHomeAuthenticator(
|
||||
ExternalUserAuthenticator webScriptsHomeAuthenticator)
|
||||
{
|
||||
this.webScriptsHomeAuthenticator = webScriptsHomeAuthenticator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res)
|
||||
{
|
||||
@@ -157,13 +139,11 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
*/
|
||||
public class RemoteUserAuthenticator extends BasicHttpAuthenticator
|
||||
{
|
||||
private static final String WEB_SCRIPTS_BASE_PATH = "org/springframework/extensions/webscripts";
|
||||
|
||||
public RemoteUserAuthenticator(WebScriptServletRequest req, WebScriptServletResponse res, AuthenticationListener listener)
|
||||
{
|
||||
super(req, res, listener);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean authenticate(RequiredAuthentication required, boolean isGuest)
|
||||
{
|
||||
@@ -179,47 +159,24 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
{
|
||||
|
||||
if (servletReq.getServiceMatch() != null &&
|
||||
isAdminConsole(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
|
||||
isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
|
||||
{
|
||||
userId = getAdminConsoleUser();
|
||||
}
|
||||
else if (servletReq.getServiceMatch() != null &&
|
||||
isWebScriptsHome(servletReq.getServiceMatch().getWebScript()) && isWebScriptsHomeAuthenticatorActive())
|
||||
{
|
||||
userId = getWebScriptsHomeUser();
|
||||
}
|
||||
|
||||
if (userId == null)
|
||||
{
|
||||
if (isAlwaysAllowBasicAuthForAdminConsole())
|
||||
{
|
||||
boolean shouldUseTimeout = shouldUseTimeoutForAdminAccessingAdminConsole(required, isGuest);
|
||||
final boolean useTimeoutForAdminAccessingAdminConsole = shouldUseTimeoutForAdminAccessingAdminConsole(required, isGuest);
|
||||
|
||||
if (shouldUseTimeout && isBasicAuthHeaderPresentForAdmin())
|
||||
if (useTimeoutForAdminAccessingAdminConsole && isBasicAuthHeaderPresentForAdmin())
|
||||
{
|
||||
return callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(required, isGuest);
|
||||
return callBasicAuthForAdminConsoleAccess(required, isGuest);
|
||||
}
|
||||
try
|
||||
{
|
||||
userId = getRemoteUserWithTimeout(shouldUseTimeout);
|
||||
}
|
||||
catch (AuthenticationTimeoutException e)
|
||||
{
|
||||
// return basic auth challenge
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (isAlwaysAllowBasicAuthForWebScriptsHome())
|
||||
{
|
||||
boolean shouldUseTimeout = shouldUseTimeoutForAdminAccessingWebScriptsHome(required, isGuest);
|
||||
|
||||
if (shouldUseTimeout && isBasicAuthHeaderPresentForAdmin())
|
||||
{
|
||||
return callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(required, isGuest);
|
||||
}
|
||||
try
|
||||
{
|
||||
userId = getRemoteUserWithTimeout(shouldUseTimeout);
|
||||
userId = getRemoteUserWithTimeout(useTimeoutForAdminAccessingAdminConsole);
|
||||
}
|
||||
catch (AuthenticationTimeoutException e)
|
||||
{
|
||||
@@ -298,63 +255,38 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
authenticated = super.authenticate(required, isGuest);
|
||||
}
|
||||
}
|
||||
if (!authenticated && servletReq.getServiceMatch() != null)
|
||||
if(!authenticated && servletReq.getServiceMatch() != null &&
|
||||
isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
|
||||
{
|
||||
WebScript webScript = servletReq.getServiceMatch().getWebScript();
|
||||
|
||||
if (isAdminConsole(webScript) && isAdminConsoleAuthenticatorActive())
|
||||
{
|
||||
adminConsoleAuthenticator.requestAuthentication(
|
||||
this.servletReq.getHttpServletRequest(),
|
||||
this.servletRes.getHttpServletResponse());
|
||||
}
|
||||
else if (isWebScriptsHome(webScript)
|
||||
&& isWebScriptsHomeAuthenticatorActive())
|
||||
{
|
||||
webScriptsHomeAuthenticator.requestAuthentication(
|
||||
this.servletReq.getHttpServletRequest(),
|
||||
this.servletRes.getHttpServletResponse());
|
||||
}
|
||||
adminConsoleAuthenticator.requestAuthentication(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
|
||||
}
|
||||
return authenticated;
|
||||
}
|
||||
|
||||
private boolean callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(RequiredAuthentication required, boolean isGuest)
|
||||
private boolean callBasicAuthForAdminConsoleAccess(RequiredAuthentication required, boolean isGuest)
|
||||
{
|
||||
// return REST call, after a timeout/basic auth challenge
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("An Admin Console or WebScripts Home request has come in with Basic Auth headers present for an admin user.");
|
||||
LOGGER.trace("An Admin Console request has come in with Basic Auth headers present for an admin user.");
|
||||
}
|
||||
// In order to prompt for another password, in case it was not entered correctly,
|
||||
// the output of this method should be returned by the calling "authenticate" method;
|
||||
// This would also mean, that once the admin basic auth header is present,
|
||||
// the authentication chain will not be used for access
|
||||
// the authentication chain will not be used for the admin console access
|
||||
return super.authenticate(required, isGuest);
|
||||
}
|
||||
|
||||
private boolean shouldUseTimeoutForAdminAccessingAdminConsole(RequiredAuthentication required, boolean isGuest)
|
||||
{
|
||||
boolean adminConsoleTimeout = RequiredAuthentication.admin.equals(required) && !isGuest &&
|
||||
servletReq.getServiceMatch() != null && isAdminConsole(servletReq.getServiceMatch().getWebScript());
|
||||
boolean useTimeoutForAdminAccessingAdminConsole = RequiredAuthentication.admin.equals(required) && !isGuest &&
|
||||
servletReq.getServiceMatch() != null && isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript());
|
||||
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("Should ensure that the admins can login with basic auth: " + adminConsoleTimeout);
|
||||
LOGGER.trace("Should ensure that the admins can login with basic auth: " + useTimeoutForAdminAccessingAdminConsole);
|
||||
}
|
||||
return adminConsoleTimeout;
|
||||
}
|
||||
|
||||
private boolean shouldUseTimeoutForAdminAccessingWebScriptsHome(RequiredAuthentication required, boolean isGuest)
|
||||
{
|
||||
boolean adminWebScriptsHomeTimeout = RequiredAuthentication.admin.equals(required) && !isGuest &&
|
||||
servletReq.getServiceMatch() != null && isWebScriptsHome(servletReq.getServiceMatch().getWebScript());
|
||||
|
||||
if (LOGGER.isTraceEnabled())
|
||||
{
|
||||
LOGGER.trace("Should ensure that the admins can login with basic auth: " + adminWebScriptsHomeTimeout);
|
||||
}
|
||||
return adminWebScriptsHomeTimeout;
|
||||
return useTimeoutForAdminAccessingAdminConsole;
|
||||
}
|
||||
|
||||
private boolean isRemoteUserMapperActive()
|
||||
@@ -367,12 +299,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
return adminConsoleAuthenticator != null && (!(adminConsoleAuthenticator instanceof ActivateableBean) || ((ActivateableBean) adminConsoleAuthenticator).isActive());
|
||||
}
|
||||
|
||||
private boolean isWebScriptsHomeAuthenticatorActive()
|
||||
{
|
||||
return webScriptsHomeAuthenticator != null && (!(webScriptsHomeAuthenticator instanceof ActivateableBean) || ((ActivateableBean) webScriptsHomeAuthenticator).isActive());
|
||||
}
|
||||
|
||||
protected boolean isAdminConsole(WebScript webScript)
|
||||
protected boolean isAdminConsoleWebScript(WebScript webScript)
|
||||
{
|
||||
if (webScript == null || adminConsoleScriptFamilies == null || webScript.getDescription() == null
|
||||
|| webScript.getDescription().getFamilys() == null)
|
||||
@@ -386,7 +313,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
}
|
||||
|
||||
// intersect the "family" sets defined
|
||||
Set<String> families = new HashSet<>(webScript.getDescription().getFamilys());
|
||||
Set<String> families = new HashSet<String>(webScript.getDescription().getFamilys());
|
||||
families.retainAll(adminConsoleScriptFamilies);
|
||||
final boolean isAdminConsole = !families.isEmpty();
|
||||
|
||||
@@ -398,23 +325,6 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
return isAdminConsole;
|
||||
}
|
||||
|
||||
protected boolean isWebScriptsHome(WebScript webScript)
|
||||
{
|
||||
if (webScript == null || webScript.toString() == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isWebScriptsHome = webScript.toString().startsWith(WEB_SCRIPTS_BASE_PATH);
|
||||
|
||||
if (LOGGER.isTraceEnabled() && isWebScriptsHome)
|
||||
{
|
||||
LOGGER.trace("Detected a WebScripts Home webscript: " + webScript);
|
||||
}
|
||||
|
||||
return isWebScriptsHome;
|
||||
}
|
||||
|
||||
protected String getRemoteUserWithTimeout(boolean useTimeout) throws AuthenticationTimeoutException
|
||||
{
|
||||
if (!useTimeout)
|
||||
@@ -484,7 +394,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
protected String getRemoteUser()
|
||||
{
|
||||
String userId = null;
|
||||
|
||||
|
||||
// If the remote user mapper is configured, we may be able to map in an externally authenticated user
|
||||
if (isRemoteUserMapperActive())
|
||||
{
|
||||
@@ -513,21 +423,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
||||
|
||||
if (isRemoteUserMapperActive())
|
||||
{
|
||||
userId = adminConsoleAuthenticator.getUserId(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
|
||||
}
|
||||
|
||||
logRemoteUserID(userId);
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
protected String getWebScriptsHomeUser()
|
||||
{
|
||||
String userId = null;
|
||||
|
||||
if (isRemoteUserMapperActive())
|
||||
{
|
||||
userId = webScriptsHomeAuthenticator.getUserId(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
|
||||
userId = adminConsoleAuthenticator.getAdminConsoleUser(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
|
||||
}
|
||||
|
||||
logRemoteUserID(userId);
|
||||
|
@@ -28,9 +28,11 @@ package org.alfresco.repo.web.scripts.transfer;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.service.cmr.transfer.TransferException;
|
||||
import org.alfresco.service.cmr.transfer.TransferReceiver;
|
||||
import org.apache.commons.fileupload2.core.FileItemInput;
|
||||
import org.apache.commons.fileupload2.core.FileItemInputIterator;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload;
|
||||
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
@@ -39,9 +41,6 @@ import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.extensions.webscripts.WrappingWebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
|
||||
|
||||
import org.alfresco.service.cmr.transfer.TransferException;
|
||||
import org.alfresco.service.cmr.transfer.TransferReceiver;
|
||||
|
||||
/**
|
||||
* This command processor is used to receive one or more content files for a given transfer.
|
||||
*
|
||||
@@ -51,9 +50,9 @@ import org.alfresco.service.cmr.transfer.TransferReceiver;
|
||||
public class PostContentCommandProcessor implements CommandProcessor
|
||||
{
|
||||
private TransferReceiver receiver;
|
||||
|
||||
|
||||
private static final String MSG_CAUGHT_UNEXPECTED_EXCEPTION = "transfer_service.receiver.caught_unexpected_exception";
|
||||
|
||||
|
||||
private static Log logger = LogFactory.getLog(PostContentCommandProcessor.class);
|
||||
|
||||
/**
|
||||
@@ -65,9 +64,12 @@ public class PostContentCommandProcessor implements CommandProcessor
|
||||
this.receiver = receiver;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */
|
||||
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest,
|
||||
* org.alfresco.web.scripts.WebScriptResponse)
|
||||
*/
|
||||
public int process(WebScriptRequest req, WebScriptResponse resp)
|
||||
{
|
||||
logger.debug("post content start");
|
||||
@@ -89,7 +91,8 @@ public class PostContentCommandProcessor implements CommandProcessor
|
||||
{
|
||||
current = null;
|
||||
}
|
||||
} while (current != null);
|
||||
}
|
||||
while (current != null);
|
||||
if (webScriptServletRequest == null)
|
||||
{
|
||||
resp.setStatus(Status.STATUS_BAD_REQUEST);
|
||||
@@ -98,7 +101,7 @@ public class PostContentCommandProcessor implements CommandProcessor
|
||||
|
||||
HttpServletRequest servletRequest = webScriptServletRequest.getHttpServletRequest();
|
||||
|
||||
// Read the transfer id from the request
|
||||
//Read the transfer id from the request
|
||||
String transferId = servletRequest.getParameter("transferId");
|
||||
|
||||
if ((transferId == null) || !JakartaServletFileUpload.isMultipartContent(servletRequest))
|
||||
@@ -121,34 +124,34 @@ public class PostContentCommandProcessor implements CommandProcessor
|
||||
logger.debug("got content Mime Part : " + name);
|
||||
receiver.saveContent(transferId, item.getName(), item.getInputStream());
|
||||
}
|
||||
}
|
||||
|
||||
// WebScriptServletRequest alfRequest = (WebScriptServletRequest)req;
|
||||
// String[] names = alfRequest.getParameterNames();
|
||||
// for(String name : names)
|
||||
// {
|
||||
// FormField item = alfRequest.getFileField(name);
|
||||
//
|
||||
// if(item != null)
|
||||
// {
|
||||
// logger.debug("got content Mime Part : " + name);
|
||||
// receiver.saveContent(transferId, item.getName(), item.getInputStream());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //TODO - should this be an exception?
|
||||
// logger.debug("Unable to get content for Mime Part : " + name);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// WebScriptServletRequest alfRequest = (WebScriptServletRequest)req;
|
||||
// String[] names = alfRequest.getParameterNames();
|
||||
// for(String name : names)
|
||||
// {
|
||||
// FormField item = alfRequest.getFileField(name);
|
||||
//
|
||||
// if(item != null)
|
||||
// {
|
||||
// logger.debug("got content Mime Part : " + name);
|
||||
// receiver.saveContent(transferId, item.getName(), item.getInputStream());
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// //TODO - should this be an exception?
|
||||
// logger.debug("Unable to get content for Mime Part : " + name);
|
||||
// }
|
||||
// }
|
||||
|
||||
logger.debug("success");
|
||||
|
||||
|
||||
resp.setStatus(Status.STATUS_OK);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.debug("exception caught", ex);
|
||||
if (transferId != null)
|
||||
if(transferId != null)
|
||||
{
|
||||
logger.debug("ending transfer", ex);
|
||||
receiver.end(transferId);
|
||||
|
@@ -27,11 +27,15 @@
|
||||
package org.alfresco.repo.web.scripts.transfer;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.alfresco.repo.transfer.TransferCommons;
|
||||
import org.alfresco.service.cmr.transfer.TransferException;
|
||||
import org.alfresco.service.cmr.transfer.TransferReceiver;
|
||||
import org.apache.commons.fileupload2.core.FileItemInput;
|
||||
import org.apache.commons.fileupload2.core.FileItemInputIterator;
|
||||
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload;
|
||||
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
@@ -40,10 +44,6 @@ import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.extensions.webscripts.WrappingWebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
|
||||
|
||||
import org.alfresco.repo.transfer.TransferCommons;
|
||||
import org.alfresco.service.cmr.transfer.TransferException;
|
||||
import org.alfresco.service.cmr.transfer.TransferReceiver;
|
||||
|
||||
/**
|
||||
* This command processor is used to receive the snapshot for a given transfer.
|
||||
*
|
||||
@@ -53,17 +53,17 @@ import org.alfresco.service.cmr.transfer.TransferReceiver;
|
||||
public class PostSnapshotCommandProcessor implements CommandProcessor
|
||||
{
|
||||
private TransferReceiver receiver;
|
||||
|
||||
|
||||
private static Log logger = LogFactory.getLog(PostSnapshotCommandProcessor.class);
|
||||
|
||||
private static final String MSG_CAUGHT_UNEXPECTED_EXCEPTION = "transfer_service.receiver.caught_unexpected_exception";
|
||||
|
||||
/* (non-Javadoc)
|
||||
*
|
||||
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */
|
||||
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse)
|
||||
*/
|
||||
public int process(WebScriptRequest req, WebScriptResponse resp)
|
||||
{
|
||||
|
||||
|
||||
int result = Status.STATUS_OK;
|
||||
// Unwrap to a WebScriptServletRequest if we have one
|
||||
WebScriptServletRequest webScriptServletRequest = null;
|
||||
@@ -83,44 +83,45 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
|
||||
{
|
||||
current = null;
|
||||
}
|
||||
} while (current != null);
|
||||
if (webScriptServletRequest == null)
|
||||
}
|
||||
while (current != null);
|
||||
if (webScriptServletRequest == null)
|
||||
{
|
||||
logger.debug("bad request, not assignable from");
|
||||
resp.setStatus(Status.STATUS_BAD_REQUEST);
|
||||
return Status.STATUS_BAD_REQUEST;
|
||||
}
|
||||
|
||||
// We can't use the WebScriptRequest version of getParameter, since that may cause the content stream
|
||||
// to be parsed. Get hold of the raw HttpServletRequest and work with that.
|
||||
|
||||
//We can't use the WebScriptRequest version of getParameter, since that may cause the content stream
|
||||
//to be parsed. Get hold of the raw HttpServletRequest and work with that.
|
||||
HttpServletRequest servletRequest = webScriptServletRequest.getHttpServletRequest();
|
||||
|
||||
// Read the transfer id from the request
|
||||
|
||||
//Read the transfer id from the request
|
||||
String transferId = servletRequest.getParameter("transferId");
|
||||
|
||||
|
||||
if ((transferId == null) || !JakartaServletFileUpload.isMultipartContent(servletRequest))
|
||||
{
|
||||
logger.debug("bad request, not multipart");
|
||||
resp.setStatus(Status.STATUS_BAD_REQUEST);
|
||||
return Status.STATUS_BAD_REQUEST;
|
||||
}
|
||||
|
||||
try
|
||||
|
||||
try
|
||||
{
|
||||
logger.debug("about to upload manifest file");
|
||||
|
||||
JakartaServletFileUpload upload = new JakartaServletFileUpload();
|
||||
FileItemInputIterator iter = upload.getItemIterator(servletRequest);
|
||||
while (iter.hasNext())
|
||||
while (iter.hasNext())
|
||||
{
|
||||
FileItemInput item = iter.next();
|
||||
if (!item.isFormField() && TransferCommons.PART_NAME_MANIFEST.equals(item.getFieldName()))
|
||||
if (!item.isFormField() && TransferCommons.PART_NAME_MANIFEST.equals(item.getFieldName()))
|
||||
{
|
||||
logger.debug("got manifest file");
|
||||
receiver.saveSnapshot(transferId, item.getInputStream());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
logger.debug("success");
|
||||
resp.setStatus(Status.STATUS_OK);
|
||||
|
||||
@@ -132,10 +133,10 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
|
||||
receiver.generateRequsite(transferId, out);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.debug("exception caught", ex);
|
||||
if (transferId != null)
|
||||
if(transferId != null)
|
||||
{
|
||||
logger.debug("ending transfer", ex);
|
||||
receiver.end(transferId);
|
||||
@@ -150,8 +151,7 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
|
||||
}
|
||||
|
||||
/**
|
||||
* @param receiver
|
||||
* the receiver to set
|
||||
* @param receiver the receiver to set
|
||||
*/
|
||||
public void setReceiver(TransferReceiver receiver)
|
||||
{
|
||||
|
@@ -21,7 +21,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.rest.api.impl;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,23 +2,23 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
@@ -38,19 +38,11 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.query.PagingRequest;
|
||||
import org.alfresco.repo.security.permissions.AccessDeniedException;
|
||||
import org.alfresco.repo.site.SiteModel;
|
||||
import org.alfresco.rest.api.Nodes;
|
||||
import org.alfresco.rest.api.People;
|
||||
@@ -85,6 +77,8 @@ import org.alfresco.util.AlfrescoCollator;
|
||||
import org.alfresco.util.ISO9075;
|
||||
import org.alfresco.util.ParameterCheck;
|
||||
import org.alfresco.util.SearchLanguageConversion;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.extensions.surf.util.I18NUtil;
|
||||
|
||||
/**
|
||||
* Queries implementation
|
||||
@@ -94,43 +88,43 @@ import org.alfresco.util.SearchLanguageConversion;
|
||||
*/
|
||||
public class QueriesImpl implements Queries, InitializingBean
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(QueriesImpl.class);
|
||||
private final static Map<String, QName> NODE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
|
||||
PARAM_NAME, ContentModel.PROP_NAME,
|
||||
PARAM_CREATEDAT, ContentModel.PROP_CREATED,
|
||||
PARAM_MODIFIEDAT, ContentModel.PROP_MODIFIED);
|
||||
private final static Map<String,QName> NODE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
|
||||
PARAM_NAME, ContentModel.PROP_NAME,
|
||||
PARAM_CREATEDAT, ContentModel.PROP_CREATED,
|
||||
PARAM_MODIFIEDAT, ContentModel.PROP_MODIFIED);
|
||||
|
||||
private final static Map<String, QName> PEOPLE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
|
||||
PARAM_PERSON_ID, ContentModel.PROP_USERNAME,
|
||||
ContentModel.PROP_FIRSTNAME,
|
||||
ContentModel.PROP_LASTNAME);
|
||||
PARAM_PERSON_ID, ContentModel.PROP_USERNAME,
|
||||
ContentModel.PROP_FIRSTNAME,
|
||||
ContentModel.PROP_LASTNAME);
|
||||
|
||||
private final static Map<String, QName> SITE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
|
||||
PARAM_SITE_ID, ContentModel.PROP_NAME,
|
||||
PARAM_SITE_TITLE, ContentModel.PROP_TITLE,
|
||||
PARAM_SITE_ID, ContentModel.PROP_NAME,
|
||||
PARAM_SITE_TITLE, ContentModel.PROP_TITLE,
|
||||
PARAM_SITE_DESCRIPTION, ContentModel.PROP_DESCRIPTION);
|
||||
|
||||
/**
|
||||
* Helper method to build a map of sort parameter names to QNames. This method iterates through the parameters. If a parameter is a String it is assumed to be a sort parameter name and will be followed by a QName to which it maps. If however it is a QName the local name of the OName is used as the sort parameter name.
|
||||
*
|
||||
* @param parameters
|
||||
* to build up the map.
|
||||
* Helper method to build a map of sort parameter names to QNames. This method iterates through
|
||||
* the parameters. If a parameter is a String it is assumed to be a sort parameter name and will
|
||||
* be followed by a QName to which it maps. If however it is a QName the local name of the OName
|
||||
* is used as the sort parameter name.
|
||||
* @param parameters to build up the map.
|
||||
* @return the map
|
||||
*/
|
||||
private static Map<String, QName> sortParamsToQNames(Object... parameters)
|
||||
{
|
||||
Map<String, QName> map = new HashMap<>();
|
||||
for (int i = 0; i < parameters.length; i++)
|
||||
for (int i=0; i<parameters.length; i++)
|
||||
{
|
||||
map.put(
|
||||
parameters[i] instanceof String
|
||||
? (String) parameters[i++]
|
||||
: ((QName) parameters[i]).getLocalName(),
|
||||
(QName) parameters[i]);
|
||||
parameters[i] instanceof String
|
||||
? (String)parameters[i++]
|
||||
: ((QName)parameters[i]).getLocalName(),
|
||||
(QName)parameters[i]);
|
||||
}
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
|
||||
private ServiceRegistry sr;
|
||||
private NodeService nodeService;
|
||||
private NamespaceService namespaceService;
|
||||
@@ -168,7 +162,7 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
ParameterCheck.mandatory("nodes", this.nodes);
|
||||
ParameterCheck.mandatory("people", this.people);
|
||||
ParameterCheck.mandatory("sites", this.sites);
|
||||
|
||||
|
||||
this.nodeService = sr.getNodeService();
|
||||
this.namespaceService = sr.getNamespaceService();
|
||||
this.dictionaryService = sr.getDictionaryService();
|
||||
@@ -179,9 +173,10 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
public CollectionWithPagingInfo<Node> findNodes(Parameters parameters)
|
||||
{
|
||||
SearchService searchService = sr.getSearchService();
|
||||
return new AbstractQuery<Node>(nodeService, searchService) {
|
||||
return new AbstractQuery<Node>(nodeService, searchService)
|
||||
{
|
||||
private final Map<String, UserInfo> mapUserInfo = new HashMap<>(10);
|
||||
|
||||
|
||||
@Override
|
||||
protected void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName)
|
||||
{
|
||||
@@ -203,14 +198,14 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
{
|
||||
query.append(")");
|
||||
}
|
||||
|
||||
|
||||
String nodeTypeStr = parameters.getParameter(PARAM_NODE_TYPE);
|
||||
if (nodeTypeStr != null)
|
||||
{
|
||||
QName filterNodeTypeQName = nodes.createQName(nodeTypeStr);
|
||||
if (dictionaryService.getType(filterNodeTypeQName) == null)
|
||||
{
|
||||
throw new InvalidArgumentException("Unknown filter nodeType: " + nodeTypeStr);
|
||||
throw new InvalidArgumentException("Unknown filter nodeType: "+nodeTypeStr);
|
||||
}
|
||||
|
||||
query.append(" AND (+TYPE:\"").append(nodeTypeStr).append(("\")"));
|
||||
@@ -234,7 +229,7 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
Path path = null;
|
||||
try
|
||||
{
|
||||
path = nodeService.getPath(nodeRef);
|
||||
path = nodeService.getPath(nodeRef);
|
||||
}
|
||||
catch (InvalidNodeRefException inre)
|
||||
{
|
||||
@@ -253,7 +248,7 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
{
|
||||
// first request for this namespace prefix, get and cache result
|
||||
Collection<String> prefixes = namespaceService.getPrefixes(qname.getNamespaceURI());
|
||||
prefix = !prefixes.isEmpty() ? prefixes.iterator().next() : "";
|
||||
prefix = prefixes.size() != 0 ? prefixes.iterator().next() : "";
|
||||
cache.put(qname.getNamespaceURI(), prefix);
|
||||
}
|
||||
buf.append('/').append(prefix).append(':').append(ISO9075.encode(qname.getLocalName()));
|
||||
@@ -267,6 +262,12 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Node> newList(int capacity)
|
||||
{
|
||||
return new ArrayList<Node>(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node convert(NodeRef nodeRef, List<String> includeParam)
|
||||
{
|
||||
@@ -280,17 +281,18 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
term = SearchLanguageConversion.escapeLuceneQuery(term);
|
||||
return term;
|
||||
}
|
||||
|
||||
|
||||
}.find(parameters, PARAM_TERM, MIN_TERM_LENGTH_NODES, "keywords",
|
||||
IN_QUERY_SORT, NODE_SORT_PARAMS_TO_QNAMES,
|
||||
new SortColumn(PARAM_MODIFIEDAT, false));
|
||||
IN_QUERY_SORT, NODE_SORT_PARAMS_TO_QNAMES,
|
||||
new SortColumn(PARAM_MODIFIEDAT, false));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CollectionWithPagingInfo<Person> findPeople(Parameters parameters)
|
||||
{
|
||||
SearchService searchService = sr.getSearchService();
|
||||
return new AbstractQuery<Person>(nodeService, searchService) {
|
||||
return new AbstractQuery<Person>(nodeService, searchService)
|
||||
{
|
||||
@Override
|
||||
protected void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName)
|
||||
{
|
||||
@@ -303,25 +305,33 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
query.append("*\")");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Person> newList(int capacity)
|
||||
{
|
||||
return new ArrayList<Person>(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Person convert(NodeRef nodeRef, List<String> includeParam)
|
||||
{
|
||||
String personId = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME);
|
||||
return people.getPerson(personId);
|
||||
Person person = people.getPerson(personId);
|
||||
return person;
|
||||
}
|
||||
|
||||
|
||||
// TODO Do the sort in the query on day. A comment in the code for the V0 API used for live people
|
||||
// search says adding sort values for this query don't work - tried it and they really don't.
|
||||
// search says adding sort values for this query don't work - tried it and they really don't.
|
||||
}.find(parameters, PARAM_TERM, MIN_TERM_LENGTH_PEOPLE, "_PERSON",
|
||||
POST_QUERY_SORT, PEOPLE_SORT_PARAMS_TO_QNAMES,
|
||||
new SortColumn(PARAM_FIRSTNAME, true), new SortColumn(PARAM_LASTNAME, true));
|
||||
POST_QUERY_SORT, PEOPLE_SORT_PARAMS_TO_QNAMES,
|
||||
new SortColumn(PARAM_FIRSTNAME, true), new SortColumn(PARAM_LASTNAME, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CollectionWithPagingInfo<Site> findSites(Parameters parameters)
|
||||
{
|
||||
SearchService searchService = sr.getSearchService();
|
||||
return new AbstractQuery<Site>(nodeService, searchService) {
|
||||
return new AbstractQuery<Site>(nodeService, searchService)
|
||||
{
|
||||
@Override
|
||||
protected void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName)
|
||||
{
|
||||
@@ -333,34 +343,44 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
query.append(term);
|
||||
query.append("*\")");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Site> newList(int capacity)
|
||||
{
|
||||
return new ArrayList<>(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Site convert(NodeRef nodeRef, List<String> includeParam)
|
||||
{
|
||||
return getSite(siteService.getSite(nodeRef));
|
||||
return getSite(siteService.getSite(nodeRef), true);
|
||||
}
|
||||
|
||||
// note: see also Sites.getSite
|
||||
private Site getSite(SiteInfo siteInfo)
|
||||
private Site getSite(SiteInfo siteInfo, boolean includeRole)
|
||||
{
|
||||
// set the site id to the short name (to deal with case sensitivity issues with using the siteId from the url)
|
||||
String siteId = siteInfo.getShortName();
|
||||
String role = sites.getSiteRole(siteId);
|
||||
String role = null;
|
||||
if(includeRole)
|
||||
{
|
||||
role = sites.getSiteRole(siteId);
|
||||
}
|
||||
return new Site(siteInfo, role);
|
||||
}
|
||||
}.find(parameters, PARAM_TERM, MIN_TERM_LENGTH_SITES, "_SITE", POST_QUERY_SORT, SITE_SORT_PARAMS_TO_QNAMES, new SortColumn(PARAM_SITE_TITLE, true));
|
||||
}
|
||||
|
||||
|
||||
public abstract static class AbstractQuery<T>
|
||||
{
|
||||
public enum Sort
|
||||
{
|
||||
IN_QUERY_SORT, POST_QUERY_SORT
|
||||
}
|
||||
|
||||
|
||||
private final NodeService nodeService;
|
||||
private final SearchService searchService;
|
||||
|
||||
|
||||
public AbstractQuery(NodeService nodeService, SearchService searchService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
@@ -368,14 +388,14 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
}
|
||||
|
||||
public CollectionWithPagingInfo<T> find(Parameters parameters,
|
||||
String termName, int minTermLength, String queryTemplateName,
|
||||
Sort sort, Map<String, QName> sortParamsToQNames, SortColumn... defaultSort)
|
||||
String termName, int minTermLength, String queryTemplateName,
|
||||
Sort sort, Map<String, QName> sortParamsToQNames, SortColumn... defaultSort)
|
||||
{
|
||||
SearchParameters sp = new SearchParameters();
|
||||
sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO);
|
||||
sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
|
||||
sp.setDefaultFieldName(queryTemplateName);
|
||||
|
||||
|
||||
String term = getTerm(parameters, termName, minTermLength);
|
||||
|
||||
StringBuilder query = new StringBuilder();
|
||||
@@ -384,7 +404,7 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
|
||||
Paging paging = parameters.getPaging();
|
||||
PagingRequest pagingRequest = Util.getPagingRequest(paging);
|
||||
|
||||
|
||||
List<SortColumn> defaultSortCols = (defaultSort != null ? Arrays.asList(defaultSort) : Collections.emptyList());
|
||||
if (sort == IN_QUERY_SORT)
|
||||
{
|
||||
@@ -393,43 +413,36 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
sp.setSkipCount(pagingRequest.getSkipCount());
|
||||
sp.setMaxItems(pagingRequest.getMaxItems());
|
||||
}
|
||||
|
||||
|
||||
ResultSet queryResults = null;
|
||||
List<T> collection = null;
|
||||
try
|
||||
{
|
||||
queryResults = searchService.query(sp);
|
||||
|
||||
|
||||
List<NodeRef> nodeRefs = queryResults.getNodeRefs();
|
||||
|
||||
Map<NodeRef, T>
|
||||
|
||||
collection = new LinkedHashMap<>(nodeRefs.size());
|
||||
|
||||
if (sort == POST_QUERY_SORT)
|
||||
{
|
||||
nodeRefs = postQuerySort(parameters, sortParamsToQNames, defaultSortCols, nodeRefs);
|
||||
}
|
||||
|
||||
collection = newList(nodeRefs.size());
|
||||
List<String> includeParam = parameters.getInclude();
|
||||
|
||||
for (NodeRef nodeRef : nodeRefs)
|
||||
{
|
||||
try
|
||||
{
|
||||
T t = convert(nodeRef, includeParam);
|
||||
collection.put(nodeRef, t);
|
||||
}
|
||||
catch (AccessDeniedException ade)
|
||||
{
|
||||
LOGGER.debug("Ignoring search result for nodeRef " + nodeRef + " due to access denied exception", ade);
|
||||
}
|
||||
T t = convert(nodeRef, includeParam);
|
||||
collection.add(t);
|
||||
}
|
||||
|
||||
if (sort == POST_QUERY_SORT)
|
||||
{
|
||||
List<T> postQuerySortedCollection = postQuerySort(parameters, sortParamsToQNames, defaultSortCols, collection.keySet())
|
||||
.stream()
|
||||
.map(collection::get)
|
||||
.toList();
|
||||
return listPage(postQuerySortedCollection, paging);
|
||||
return listPage(collection, paging);
|
||||
}
|
||||
else
|
||||
{
|
||||
return CollectionWithPagingInfo.asPaged(paging, collection.values(), queryResults.hasMore(), Long.valueOf(queryResults.getNumberFound()).intValue());
|
||||
return CollectionWithPagingInfo.asPaged(paging, collection, queryResults.hasMore(), Long.valueOf(queryResults.getNumberFound()).intValue());
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -442,39 +455,40 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds up the query and is expected to call {@link SearchParameters#setDefaultFieldName(String)} and {@link SearchParameters#addQueryTemplate(String, String)}
|
||||
*
|
||||
* @param query
|
||||
* StringBuilder into which the query should be built.
|
||||
* @param term
|
||||
* to be searched for
|
||||
* @param sp
|
||||
* SearchParameters
|
||||
* Builds up the query and is expected to call {@link SearchParameters#setDefaultFieldName(String)}
|
||||
* and {@link SearchParameters#addQueryTemplate(String, String)}
|
||||
* @param query StringBuilder into which the query should be built.
|
||||
* @param term to be searched for
|
||||
* @param sp SearchParameters
|
||||
* @param queryTemplateName
|
||||
*/
|
||||
protected abstract void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName);
|
||||
|
||||
/**
|
||||
* Returns a list of the correct type.
|
||||
* @param capacity of the list
|
||||
* @return a new list.
|
||||
*/
|
||||
protected abstract List<T> newList(int capacity);
|
||||
|
||||
/**
|
||||
* Converts a nodeRef into the an object of the required type.
|
||||
*
|
||||
* @param nodeRef
|
||||
* to be converted
|
||||
* @param includeParam
|
||||
* additional fields to be included
|
||||
* @param nodeRef to be converted
|
||||
* @param includeParam additional fields to be included
|
||||
* @return the object
|
||||
*/
|
||||
protected abstract T convert(NodeRef nodeRef, List<String> includeParam);
|
||||
|
||||
|
||||
protected String getTerm(Parameters parameters, String termName, int minTermLength)
|
||||
{
|
||||
String term = parameters.getParameter(termName);
|
||||
if (term == null)
|
||||
{
|
||||
throw new InvalidArgumentException("Query '" + termName + "' not specified");
|
||||
throw new InvalidArgumentException("Query '"+termName+"' not specified");
|
||||
}
|
||||
|
||||
|
||||
term = escapeTerm(term);
|
||||
|
||||
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < term.length(); i++)
|
||||
{
|
||||
@@ -491,7 +505,7 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
|
||||
if (cnt < minTermLength)
|
||||
{
|
||||
throw new InvalidArgumentException("Query '" + termName + "' is too short. Must have at least " + minTermLength + " alphanumeric chars");
|
||||
throw new InvalidArgumentException("Query '"+termName+"' is too short. Must have at least "+minTermLength+" alphanumeric chars");
|
||||
}
|
||||
|
||||
return term;
|
||||
@@ -510,12 +524,12 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
term = SearchLanguageConversion.escapeLuceneQuery(term);
|
||||
return term;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds sort order to the SearchParameters.
|
||||
*/
|
||||
protected void addSortOrder(Parameters parameters, Map<String, QName> sortParamsToQNames,
|
||||
List<SortColumn> defaultSortCols, SearchParameters sp)
|
||||
List<SortColumn> defaultSortCols, SearchParameters sp)
|
||||
{
|
||||
List<SortColumn> sortCols = getSorting(parameters, defaultSortCols);
|
||||
for (SortColumn sortCol : sortCols)
|
||||
@@ -523,16 +537,16 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
QName sortPropQName = sortParamsToQNames.get(sortCol.column);
|
||||
if (sortPropQName == null)
|
||||
{
|
||||
throw new InvalidArgumentException("Invalid sort field: " + sortCol.column);
|
||||
throw new InvalidArgumentException("Invalid sort field: "+sortCol.column);
|
||||
}
|
||||
sp.addSort("@" + sortPropQName, sortCol.asc);
|
||||
sp.addSort("@" + sortPropQName, sortCol.asc);
|
||||
}
|
||||
}
|
||||
|
||||
private List<SortColumn> getSorting(Parameters parameters, List<SortColumn> defaultSortCols)
|
||||
{
|
||||
List<SortColumn> sortCols = parameters.getSorting();
|
||||
if (sortCols == null || sortCols.isEmpty())
|
||||
if (sortCols == null || sortCols.size() == 0)
|
||||
{
|
||||
sortCols = defaultSortCols == null ? Collections.emptyList() : defaultSortCols;
|
||||
}
|
||||
@@ -540,66 +554,63 @@ public class QueriesImpl implements Queries, InitializingBean
|
||||
}
|
||||
|
||||
protected List<NodeRef> postQuerySort(Parameters parameters, Map<String, QName> sortParamsToQNames,
|
||||
List<SortColumn> defaultSortCols, Set<NodeRef> unsortedNodeRefs)
|
||||
List<SortColumn> defaultSortCols, List<NodeRef> nodeRefs)
|
||||
{
|
||||
final List<SortColumn> sortCols = getSorting(parameters, defaultSortCols);
|
||||
int sortColCount = sortCols.size();
|
||||
|
||||
if (sortColCount == 0)
|
||||
if (sortColCount > 0)
|
||||
{
|
||||
return new ArrayList<>(unsortedNodeRefs);
|
||||
}
|
||||
|
||||
// make copy of nodeRefs because it can be unmodifiable list.
|
||||
List<NodeRef> sortedNodeRefs = new ArrayList<>(unsortedNodeRefs);
|
||||
|
||||
List<QName> sortPropQNames = new ArrayList<>(sortColCount);
|
||||
for (SortColumn sortCol : sortCols)
|
||||
{
|
||||
QName sortPropQName = sortParamsToQNames.get(sortCol.column);
|
||||
if (sortPropQName == null)
|
||||
// make copy of nodeRefs because it can be unmodifiable list.
|
||||
nodeRefs = new ArrayList<NodeRef>(nodeRefs);
|
||||
|
||||
List<QName> sortPropQNames = new ArrayList<>(sortColCount);
|
||||
for (SortColumn sortCol : sortCols)
|
||||
{
|
||||
throw new InvalidArgumentException("Invalid sort field: " + sortCol.column);
|
||||
}
|
||||
sortPropQNames.add(sortPropQName);
|
||||
}
|
||||
|
||||
final Collator col = AlfrescoCollator.getInstance(I18NUtil.getLocale());
|
||||
Collections.sort(sortedNodeRefs, new Comparator<NodeRef>() {
|
||||
@Override
|
||||
public int compare(NodeRef n1, NodeRef n2)
|
||||
{
|
||||
int result = 0;
|
||||
for (int i = 0; i < sortCols.size(); i++)
|
||||
QName sortPropQName = sortParamsToQNames.get(sortCol.column);
|
||||
if (sortPropQName == null)
|
||||
{
|
||||
SortColumn sortCol = sortCols.get(i);
|
||||
QName sortPropQName = sortPropQNames.get(i);
|
||||
throw new InvalidArgumentException("Invalid sort field: "+sortCol.column);
|
||||
}
|
||||
sortPropQNames.add(sortPropQName);
|
||||
}
|
||||
|
||||
final Collator col = AlfrescoCollator.getInstance(I18NUtil.getLocale());
|
||||
Collections.sort(nodeRefs, new Comparator<NodeRef>()
|
||||
{
|
||||
@Override
|
||||
public int compare(NodeRef n1, NodeRef n2)
|
||||
{
|
||||
int result = 0;
|
||||
for (int i=0; i<sortCols.size(); i++)
|
||||
{
|
||||
SortColumn sortCol = sortCols.get(i);
|
||||
QName sortPropQName = sortPropQNames.get(i);
|
||||
|
||||
Serializable p1 = getProperty(n1, sortPropQName);
|
||||
Serializable p2 = getProperty(n2, sortPropQName);
|
||||
|
||||
Serializable p1 = getProperty(n1, sortPropQName);
|
||||
Serializable p2 = getProperty(n2, sortPropQName);
|
||||
|
||||
result = ((p1 instanceof Long) && (p2 instanceof Long)
|
||||
? Long.compare((Long) p1, (Long) p2)
|
||||
result = ((p1 instanceof Long) && (p2 instanceof Long)
|
||||
? Long.compare((Long)p1, (Long)p2)
|
||||
: col.compare(p1.toString(), p2.toString()))
|
||||
* (sortCol.asc ? 1 : -1);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
break;
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Serializable getProperty(NodeRef nodeRef, QName sortPropQName)
|
||||
{
|
||||
Serializable result = nodeService.getProperty(nodeRef, sortPropQName);
|
||||
return result == null ? "" : result;
|
||||
}
|
||||
private Serializable getProperty(NodeRef nodeRef, QName sortPropQName)
|
||||
{
|
||||
Serializable result = nodeService.getProperty(nodeRef, sortPropQName);
|
||||
return result == null ? "" : result;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return sortedNodeRefs;
|
||||
});
|
||||
}
|
||||
return nodeRefs;
|
||||
}
|
||||
|
||||
// note: see also AbstractNodeRelation
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -39,9 +39,6 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
|
||||
import org.alfresco.repo.action.executer.CheckOutActionExecuter;
|
||||
import org.alfresco.repo.action.executer.CopyActionExecuter;
|
||||
import org.alfresco.repo.action.executer.ImageTransformActionExecuter;
|
||||
@@ -61,6 +58,8 @@ import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.namespace.NamespaceService;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
|
||||
/**
|
||||
* This class provides logic for validation of permissions for action parameters which reference node.
|
||||
@@ -68,14 +67,15 @@ import org.alfresco.service.namespace.NamespaceService;
|
||||
public class ActionNodeParameterValidator implements ActionValidator
|
||||
{
|
||||
/**
|
||||
* This list holds action parameter names which require only READ permission on a referenced node That means, all other parameters that reference nodes will require WRITE permission
|
||||
* This list holds action parameter names which require only READ permission on a referenced node
|
||||
* That means, all other parameters that reference nodes will require WRITE permission
|
||||
*/
|
||||
static final Map<String, List<String>> REQUIRE_READ_PERMISSION_PARAMS = Map.of(LinkCategoryActionExecuter.NAME, List.of(LinkCategoryActionExecuter.PARAM_CATEGORY_VALUE));
|
||||
static final Map<String, List<String>> REQUIRE_READ_PERMISSION_PARAMS =
|
||||
Map.of(LinkCategoryActionExecuter.NAME, List.of(LinkCategoryActionExecuter.PARAM_CATEGORY_VALUE));
|
||||
|
||||
static final String NO_PROPER_PERMISSIONS_FOR_NODE = "No proper permissions for node: ";
|
||||
static final String NOT_A_CATEGORY = "Node is not a category ";
|
||||
static final String NOT_A_FOLDER = "Node is not a folder ";
|
||||
static final String NO_LONGER_EXISTS = "%s having Id: %s no longer exists. Please update your rule definition.";
|
||||
|
||||
private final Actions actions;
|
||||
private final NamespaceService namespaceService;
|
||||
@@ -83,7 +83,7 @@ public class ActionNodeParameterValidator implements ActionValidator
|
||||
private final PermissionService permissionService;
|
||||
|
||||
public ActionNodeParameterValidator(Actions actions, NamespaceService namespaceService, Nodes nodes,
|
||||
PermissionService permissionService)
|
||||
PermissionService permissionService)
|
||||
{
|
||||
this.actions = actions;
|
||||
this.namespaceService = namespaceService;
|
||||
@@ -94,8 +94,7 @@ public class ActionNodeParameterValidator implements ActionValidator
|
||||
/**
|
||||
* Validates action parameters that reference nodes against access permissions for executing user.
|
||||
*
|
||||
* @param action
|
||||
* Action to be validated
|
||||
* @param action Action to be validated
|
||||
*/
|
||||
@Override
|
||||
public void validate(Action action)
|
||||
@@ -125,7 +124,7 @@ public class ActionNodeParameterValidator implements ActionValidator
|
||||
}
|
||||
|
||||
private void validateNodes(final List<ActionDefinition.ParameterDefinition> nodeRefParamDefinitions,
|
||||
final Action action)
|
||||
final Action action)
|
||||
{
|
||||
if (MapUtils.isNotEmpty(action.getParams()))
|
||||
{
|
||||
@@ -133,15 +132,7 @@ public class ActionNodeParameterValidator implements ActionValidator
|
||||
.filter(pd -> action.getParams().containsKey(pd.getName()))
|
||||
.forEach(p -> {
|
||||
final String nodeId = Objects.toString(action.getParams().get(p.getName()), Strings.EMPTY);
|
||||
NodeRef nodeRef;
|
||||
try
|
||||
{
|
||||
nodeRef = nodes.validateNode(nodeId);
|
||||
}
|
||||
catch (EntityNotFoundException e)
|
||||
{
|
||||
throw new EntityNotFoundException(String.format(NO_LONGER_EXISTS, p.getDisplayLabel(), nodeId), e);
|
||||
}
|
||||
final NodeRef nodeRef = nodes.validateNode(nodeId);
|
||||
validatePermission(action.getActionDefinitionId(), p.getName(), nodeRef);
|
||||
validateType(action.getActionDefinitionId(), nodeRef);
|
||||
});
|
||||
@@ -172,8 +163,7 @@ public class ActionNodeParameterValidator implements ActionValidator
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_A_FOLDER + nodeRef.getId());
|
||||
}
|
||||
}
|
||||
else if (!nodes.nodeMatches(nodeRef, Set.of(TYPE_CATEGORY), Collections.emptySet()))
|
||||
} else if (!nodes.nodeMatches(nodeRef, Set.of(TYPE_CATEGORY), Collections.emptySet()))
|
||||
{
|
||||
throw new InvalidArgumentException(NOT_A_CATEGORY + nodeRef.getId());
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Admin Console
|
||||
admin-console.help=Help
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Successfully saved values.
|
||||
|
||||
admin-console.host=Host
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Konzole pro spr\u00e1vce
|
||||
admin-console.help=N\u00e1pov\u011bda
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Hodnoty byly \u00fasp\u011b\u0161n\u011b ulo\u017eeny.
|
||||
|
||||
admin-console.host=Hostitel
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Administrationskonsol
|
||||
admin-console.help=Hj\u00e6lp
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=V\u00e6rdierne blev gemt.
|
||||
|
||||
admin-console.host=V\u00e6rt
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Administratorkonsole
|
||||
admin-console.help=Hilfe
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Erfolgreich gespeicherte Werte.
|
||||
|
||||
admin-console.host=Host
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Consola de administraci\u00f3n
|
||||
admin-console.help=Ayuda
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Valores guardados correctamente.
|
||||
|
||||
admin-console.host=Host
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Hallintakonsoli
|
||||
admin-console.help=Ohje
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Arvot tallennettiin.
|
||||
|
||||
admin-console.host=Is\u00e4nt\u00e4
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Console d'administration
|
||||
admin-console.help=Aide
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Les valeurs ont bien \u00e9t\u00e9 enregistr\u00e9es.
|
||||
|
||||
admin-console.host=H\u00f4te
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Console di amministrazione
|
||||
admin-console.help=Aiuto
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=I valori sono stati salvati.
|
||||
|
||||
admin-console.host=Host
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=\u7ba1\u7406\u30b3\u30f3\u30bd\u30fc\u30eb
|
||||
admin-console.help=\u30d8\u30eb\u30d7
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=\u5024\u3092\u6b63\u5e38\u306b\u4fdd\u5b58\u3057\u307e\u3057\u305f\u3002
|
||||
|
||||
admin-console.host=\u30db\u30b9\u30c8
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Admin-konsoll
|
||||
admin-console.help=Hjelp
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Verdier som ble lagret.
|
||||
|
||||
admin-console.host=Vert
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Beheerconsole
|
||||
admin-console.help=Help
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Waarden zijn opgeslagen.
|
||||
|
||||
admin-console.host=Host
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Konsola administracyjna
|
||||
admin-console.help=Pomoc
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Warto\u015bci zosta\u0142y zapisane pomy\u015blnie.
|
||||
|
||||
admin-console.host=Host
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Console de administra\u00e7\u00e3o
|
||||
admin-console.help=Ajuda
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Valores salvos com sucesso.
|
||||
|
||||
admin-console.host=Host
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=\u041a\u043e\u043d\u0441\u043e\u043b\u044c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430
|
||||
admin-console.help=\u0421\u043f\u0440\u0430\u0432\u043a\u0430
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f.
|
||||
|
||||
admin-console.host=\u0425\u043e\u0441\u0442
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Admin-konsol
|
||||
admin-console.help=Hj\u00e4lp
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=V\u00e4rden sparades.
|
||||
|
||||
admin-console.host=V\u00e4rd
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=\u7ba1\u7406\u63a7\u5236\u53f0
|
||||
admin-console.help=\u5e2e\u52a9
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=\u5df2\u6210\u529f\u4fdd\u5b58\u7684\u503c\u3002
|
||||
|
||||
admin-console.host=\u4e3b\u673a
|
||||
|
@@ -5,4 +5,4 @@
|
||||
<authentication>guest</authentication>
|
||||
<transaction allow="readonly">required</transaction>
|
||||
<lifecycle>internal</lifecycle>
|
||||
</webscript>
|
||||
</webscript>
|
@@ -12,9 +12,9 @@
|
||||
<#macro page title readonly=false controller=DEFAULT_CONTROLLER!"/admin" params="" dialog=false>
|
||||
<#assign FORM_ID="admin-jmx-form" />
|
||||
<#if server.edition == "Community">
|
||||
<#assign docsEdition = "/Alfresco-Content-Services-Community-Edition/" + server.getVersionMajor() + "." + server.getVersionMinor() + "/Alfresco-Content-Services-Community-Edition" />
|
||||
<#assign docsEdition = "community" />
|
||||
<#elseif server.edition == "Enterprise" >
|
||||
<#assign docsEdition = "/Alfresco-Content-Services/" + server.getVersionMajor() + "." + server.getVersionMinor() + "/Alfresco-Content-Services" />
|
||||
<#assign docsEdition = server.getVersionMajor() + "." + server.getVersionMinor() />
|
||||
</#if>
|
||||
<#if metadata??>
|
||||
<#assign HOSTNAME>${msg("admin-console.host")}: ${metadata.hostname}</#assign>
|
||||
@@ -551,7 +551,7 @@ Admin.addEventListener(window, 'load', function() {
|
||||
Template for a full page view
|
||||
-->
|
||||
<div class="sticky-wrapper">
|
||||
|
||||
|
||||
<div class="header">
|
||||
<span><a href="${url.serviceContext}${DEFAULT_CONTROLLER!"/admin"}">${msg("admin-console.header")}</a></span><#if metadata??><span class="meta">${HOSTNAME}</span><span class="meta">${HOSTADDR}</span></#if>
|
||||
<div style="float:right"><a href="${msg("admin-console.help-link", docsEdition)}" target="_blank">${msg("admin-console.help")}</a></div>
|
||||
@@ -908,4 +908,4 @@ Admin.addEventListener(window, 'load', function() {
|
||||
<#macro button label description="" onclick="" style="" id="" class="" disabled="false">
|
||||
<input class="<#if class?has_content>${class?html}<#else>inline</#if>" <#if id?has_content>id="${id?html}"</#if> <#if style?has_content>style="${style?html}"</#if> type="button" value="${label?html}" onclick="${onclick?html}" <#if disabled="true">disabled="true"</#if> />
|
||||
<#if description?has_content><span class="description">${description?html}</span></#if>
|
||||
</#macro>
|
||||
</#macro>
|
@@ -214,13 +214,9 @@
|
||||
<property name="authenticationListener" ref="webScriptAuthenticationListener"/>
|
||||
<property name="remoteUserMapper" ref="RemoteUserMapper" />
|
||||
<property name="adminConsoleAuthenticator" ref="AdminConsoleAuthenticator" />
|
||||
<property name="webScriptsHomeAuthenticator" ref="WebScriptsHomeAuthenticator" />
|
||||
<property name="alwaysAllowBasicAuthForAdminConsole">
|
||||
<value>${authentication.alwaysAllowBasicAuthForAdminConsole.enabled}</value>
|
||||
</property>
|
||||
<property name="alwaysAllowBasicAuthForWebScriptsHome">
|
||||
<value>${authentication.alwaysAllowBasicAuthForWebScriptsHome.enabled}</value>
|
||||
</property>
|
||||
<property name="getRemoteUserTimeoutMilliseconds">
|
||||
<value>${authentication.getRemoteUserTimeoutMilliseconds}</value>
|
||||
</property>
|
||||
|
@@ -25,20 +25,6 @@
|
||||
*/
|
||||
package org.alfresco.rest.api.tests;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.rest.AbstractSingleNetworkSiteTest;
|
||||
import org.alfresco.rest.api.Queries;
|
||||
@@ -50,12 +36,27 @@ import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.search.SearchParameters;
|
||||
import org.alfresco.service.cmr.site.SiteService;
|
||||
import org.alfresco.service.cmr.site.SiteVisibility;
|
||||
import org.alfresco.util.testing.category.LuceneTests;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.categories.Category;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* V1 REST API tests for pre-defined 'live' search Queries on Sites
|
||||
* V1 REST API tests for pre-defined 'live' search Queries on Sites
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/queries/sites}</li>
|
||||
* <li> {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/queries/sites} </li>
|
||||
* </ul>
|
||||
*
|
||||
* @author janv
|
||||
@@ -63,7 +64,7 @@ import org.alfresco.service.cmr.site.SiteVisibility;
|
||||
public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
{
|
||||
private static final String URL_QUERIES_LSS = "queries/sites";
|
||||
|
||||
|
||||
private SiteService siteService;
|
||||
|
||||
@Before
|
||||
@@ -71,7 +72,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
public void setup() throws Exception
|
||||
{
|
||||
super.setup();
|
||||
siteService = (SiteService) applicationContext.getBean("SiteService");
|
||||
siteService = (SiteService)applicationContext.getBean("SiteService");
|
||||
}
|
||||
|
||||
// Note expectedIds defaults to ids
|
||||
@@ -85,7 +86,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
}
|
||||
|
||||
dummySearchServiceQueryNodeRefs.clear();
|
||||
for (String id : ids)
|
||||
for (String id: ids)
|
||||
{
|
||||
NodeRef nodeRef = getNodeRef(id);
|
||||
dummySearchServiceQueryNodeRefs.add(nodeRef);
|
||||
@@ -97,7 +98,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
if (expectedStatus == 200)
|
||||
{
|
||||
String termWithEscapedAsterisks = term.replaceAll("\\*", "\\\\*");
|
||||
String expectedQuery = "TYPE:\"{http://www.alfresco.org/model/site/1.0}site\" AND (\"*" + termWithEscapedAsterisks + "*\")";
|
||||
String expectedQuery = "TYPE:\"{http://www.alfresco.org/model/site/1.0}site\" AND (\"*"+ termWithEscapedAsterisks +"*\")";
|
||||
ArgumentCaptor<SearchParameters> searchParametersCaptor = ArgumentCaptor.forClass(SearchParameters.class);
|
||||
verify(mockSearchService, times(++callCountToMockSearchService)).query(searchParametersCaptor.capture());
|
||||
SearchParameters parameters = searchParametersCaptor.getValue();
|
||||
@@ -108,32 +109,30 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
|
||||
if (orderBy != null)
|
||||
{
|
||||
for (int i = 0; i < expectedIds.length; i++)
|
||||
for (int i=0; i<expectedIds.length; i++)
|
||||
{
|
||||
String id = expectedIds[i];
|
||||
String actualId = sites.get(i).getId();
|
||||
assertEquals("Order " + i + ":", id, actualId);
|
||||
assertEquals("Order "+i+":", id, actualId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Tests basic api for nodes live search sites - metadata (id, title, description)
|
||||
*
|
||||
* <p>
|
||||
* GET:
|
||||
* </p>
|
||||
* <p>GET:</p>
|
||||
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/queries/sites}
|
||||
*/
|
||||
@Test
|
||||
public void testLiveSearchSites() throws Exception
|
||||
{
|
||||
setRequestContext(user1);
|
||||
AuthenticationUtil.setFullyAuthenticatedUser(user1);
|
||||
|
||||
int sCount = 5;
|
||||
assertTrue(sCount > 4); // as relied on by test below
|
||||
|
||||
|
||||
List<String> siteIds = new ArrayList<>(sCount);
|
||||
|
||||
try
|
||||
@@ -150,14 +149,14 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
String siteD = "siteD";
|
||||
|
||||
int charValue = siteI.charAt(0);
|
||||
|
||||
|
||||
// create some some sites with site id: ab00001, abc00002, abcd00003, abcde00004, abcdef00005 (and some specific titles and descriptions)
|
||||
for (int i = 1; i <= sCount; i++)
|
||||
{
|
||||
String num = String.format("%05d", i);
|
||||
|
||||
charValue = charValue + 1;
|
||||
siteI = siteI + String.valueOf((char) charValue);
|
||||
charValue = charValue+1;
|
||||
siteI = siteI + String.valueOf((char)charValue);
|
||||
|
||||
String siteId = siteI + num + RUNID;
|
||||
String siteTitle = siteT + num + siteT;
|
||||
@@ -221,7 +220,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
{
|
||||
// some cleanup
|
||||
setRequestContext(user1);
|
||||
|
||||
|
||||
for (String siteId : siteIds)
|
||||
{
|
||||
deleteSite(siteId, true, 204);
|
||||
@@ -231,20 +230,14 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
|
||||
private NodeRef getNodeRef(String createdSiteId)
|
||||
{
|
||||
// Created sites do not return NodeRefs to the caller so we need to get the NodeRef from the siteService.
|
||||
// Temporarily as admin we will get NodeRefs to handle ACL authorization.
|
||||
String userUnderTest = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
AuthenticationUtil.setFullyAuthenticatedUser(DEFAULT_ADMIN);
|
||||
|
||||
AuthenticationUtil.setFullyAuthenticatedUser(user1);
|
||||
// The following call to siteService.getSite(createdSiteId).getNodeRef() returns a NodeRef like:
|
||||
// workspace://SpacesStore/9db76769-96de-4de4-bdb4-a127130af362
|
||||
// workspace://SpacesStore/9db76769-96de-4de4-bdb4-a127130af362
|
||||
// We call tenantService.getName(nodeRef) to get a fully qualified NodeRef as Solr returns this.
|
||||
// They look like:
|
||||
// workspace://@org.alfresco.rest.api.tests.queriespeopleapitest@SpacesStore/9db76769-96de-4de4-bdb4-a127130af362
|
||||
// workspace://@org.alfresco.rest.api.tests.queriespeopleapitest@SpacesStore/9db76769-96de-4de4-bdb4-a127130af362
|
||||
NodeRef nodeRef = siteService.getSite(createdSiteId).getNodeRef();
|
||||
nodeRef = tenantService.getName(nodeRef);
|
||||
|
||||
AuthenticationUtil.setFullyAuthenticatedUser(userUnderTest);
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
@@ -252,7 +245,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
public void testLiveSearchSites_SortPage() throws Exception
|
||||
{
|
||||
setRequestContext(user1);
|
||||
AuthenticationUtil.setFullyAuthenticatedUser(user1);
|
||||
|
||||
List<String> siteIds = new ArrayList<>(5);
|
||||
|
||||
try
|
||||
@@ -260,14 +253,14 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
// As user 1 ...
|
||||
|
||||
Paging paging = getPaging(0, 100);
|
||||
|
||||
|
||||
// create site
|
||||
String s1 = createSite("siABCDEF" + RUNID, "ABCDEF DEF", "sdABCDEF", SiteVisibility.PRIVATE, 201).getId();
|
||||
String s2 = createSite("siABCD" + RUNID, "ABCD DEF", "sdABCD", SiteVisibility.PRIVATE, 201).getId();
|
||||
String s3 = createSite("siABCDE" + RUNID, "ABCDE DEF", "sdABCDE", SiteVisibility.PRIVATE, 201).getId();
|
||||
String s4 = createSite("siAB" + RUNID, "AB DEF", "sdAB", SiteVisibility.PRIVATE, 201).getId();
|
||||
String s5 = createSite("siABC" + RUNID, "ABC DEF", "sdABC", SiteVisibility.PRIVATE, 201).getId();
|
||||
|
||||
String s1 = createSite("siABCDEF"+RUNID, "ABCDEF DEF", "sdABCDEF", SiteVisibility.PRIVATE, 201).getId();
|
||||
String s2 = createSite("siABCD"+RUNID, "ABCD DEF", "sdABCD", SiteVisibility.PRIVATE, 201).getId();
|
||||
String s3 = createSite("siABCDE"+RUNID, "ABCDE DEF", "sdABCDE", SiteVisibility.PRIVATE, 201).getId();
|
||||
String s4 = createSite("siAB"+RUNID, "AB DEF", "sdAB", SiteVisibility.PRIVATE, 201).getId();
|
||||
String s5 = createSite("siABC"+RUNID, "ABC DEF", "sdABC", SiteVisibility.PRIVATE, 201).getId();
|
||||
|
||||
// test sort order
|
||||
{
|
||||
// default sort order - title asc
|
||||
@@ -283,11 +276,11 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
// basic paging tests
|
||||
{
|
||||
// sort order - title desc
|
||||
checkApiCall("siAB", "title desc", getPaging(0, 2), 200, new String[]{s1, s3}, s1, s3, s2, s5, s4);
|
||||
checkApiCall("siAB", "title desc", getPaging(2, 2), 200, new String[]{s2, s5}, s1, s3, s2, s5, s4);
|
||||
checkApiCall("siAB", "title desc", getPaging(4, 2), 200, new String[]{s4}, s1, s3, s2, s5, s4);
|
||||
checkApiCall("siAB", "title desc", getPaging(0, 2), 200, new String[] {s1, s3}, s1, s3, s2, s5, s4);
|
||||
checkApiCall("siAB", "title desc", getPaging(2, 2), 200, new String[] {s2, s5}, s1, s3, s2, s5, s4);
|
||||
checkApiCall("siAB", "title desc", getPaging(4, 2), 200, new String[] {s4}, s1, s3, s2, s5, s4);
|
||||
}
|
||||
|
||||
|
||||
// -ve tests
|
||||
{
|
||||
// -ve test - invalid sort field
|
||||
@@ -311,52 +304,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the search service do not support ACL filtering, then the Queries API should handle the response to exclude private sites and potential unauthorized error when building response.
|
||||
*/
|
||||
@Test
|
||||
public void testLiveSearchExcludesPrivateSites() throws Exception
|
||||
{
|
||||
String publicSiteId = null;
|
||||
String privateSiteId = null;
|
||||
try
|
||||
{
|
||||
// given
|
||||
setRequestContext(null, DEFAULT_ADMIN, DEFAULT_ADMIN_PWD);
|
||||
createUser("bartender");
|
||||
|
||||
publicSiteId = createSite("samePrefixPublicSite", "samePrefixPublicSite", "Visible to all users", SiteVisibility.PUBLIC, 201).getId();
|
||||
privateSiteId = createSite("samePrefixPrivateSite", "samePrefixPrivateSite", "Hidden from bartender", SiteVisibility.PRIVATE, 201).getId();
|
||||
|
||||
String[] searchResults = {publicSiteId, privateSiteId};
|
||||
String[] expectedSites = {publicSiteId};
|
||||
|
||||
// when
|
||||
setRequestContext(null, "bartender", "password");
|
||||
AuthenticationUtil.setFullyAuthenticatedUser("bartender");
|
||||
|
||||
// then
|
||||
checkApiCall("samePrefix", null, getPaging(0, 100), 200, expectedSites, searchResults);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// cleanup
|
||||
AuthenticationUtil.setFullyAuthenticatedUser(DEFAULT_ADMIN);
|
||||
setRequestContext(null, DEFAULT_ADMIN, DEFAULT_ADMIN_PWD);
|
||||
if (publicSiteId != null)
|
||||
{
|
||||
deleteSite(publicSiteId, true, 204);
|
||||
}
|
||||
if (privateSiteId != null)
|
||||
{
|
||||
deleteSite(privateSiteId, true, 204);
|
||||
}
|
||||
deleteUser("bartender", null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getScope()
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -212,7 +212,7 @@ public class ProcessesImplTest extends TestCase implements RecognizedParamsExtra
|
||||
{
|
||||
// the tests are always run on PostgreSQL only
|
||||
// Dialect dialect = (Dialect) applicationContext.getBean("dialect");
|
||||
// if (dialect instanceof SQLServerDialect)
|
||||
// if (dialect instanceof AlfrescoSQLServerDialect)
|
||||
// {
|
||||
// REPO-1104: we do not run this test on MS SQL server because it will fail
|
||||
// until the Activiti defect related to REPO-1104 will be fixed
|
||||
|
@@ -27,7 +27,7 @@ to integrate with a number of external Authentication providers including
|
||||
* https://github.com/Alfresco/alfresco-data-model/tree/master/src/main/java/org/alfresco/repo/security/authentication
|
||||
* License: LGPL
|
||||
* Issue Tracker Link: https://issues.alfresco.com/jira/issues/?jql=project%3DREPO
|
||||
* Documentation Link: https://support.hyland.com/r/Alfresco/Alfresco-Content-Services-Community-Edition/23.4/Alfresco-Content-Services-Community-Edition/Administer/Manage-Security/Authentication-and-sync
|
||||
* Documentation Link: http://docs.alfresco.com/5.2/concepts/auth-intro.html
|
||||
* Contribution Model: Alfresco Open Source
|
||||
***
|
||||
|
||||
|
@@ -16,7 +16,7 @@
|
||||
* Source Code Link:m https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/
|
||||
* License: LGPL
|
||||
* Issue Tracker Link: https://issues.alfresco.com/jira/secure/RapidBoard.jspa?projectKey=REPO&useStoredSettings=true&rapidView=379
|
||||
* Documentation Link: https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Configure/Repository/About-Versioning
|
||||
* Documentation Link: http://docs.alfresco.com/5.1/concepts/versioning.html
|
||||
* Contribution Model: Alfresco publishes the source code and will review proposed patch requests
|
||||
***
|
||||
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>23.6.0.5</version>
|
||||
<version>23.4.2.5-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
@@ -85,7 +85,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
|
||||
<artifactId>commons-fileupload2-jakarta</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
@@ -2,96 +2,93 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.action.executer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.action.ParameterDefinitionImpl;
|
||||
import org.alfresco.repo.action.access.ActionAccessRestriction;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.action.Action;
|
||||
import org.alfresco.service.cmr.action.ParameterDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
|
||||
/**
|
||||
* Add features action executor implementation.
|
||||
*
|
||||
* @author Roy Wetherall
|
||||
*/
|
||||
public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase
|
||||
{
|
||||
/**
|
||||
* Action constants
|
||||
*/
|
||||
public static final String NAME = "add-features";
|
||||
public static final String PARAM_ASPECT_NAME = "aspect-name";
|
||||
public static final String PARAM_CONSTRAINT = "ac-aspects";
|
||||
|
||||
/**
|
||||
* The node service
|
||||
*/
|
||||
private NodeService nodeService;
|
||||
|
||||
/** Transaction Service, used for retrying operations */
|
||||
private TransactionService transactionService;
|
||||
|
||||
/**
|
||||
* Set the node service
|
||||
*
|
||||
* @param nodeService
|
||||
* the node service
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction service
|
||||
*
|
||||
* @param transactionService
|
||||
* the transaction service
|
||||
*/
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adhoc properties are allowed for this executor
|
||||
*/
|
||||
@Override
|
||||
protected boolean getAdhocPropertiesAllowed()
|
||||
{
|
||||
return true;
|
||||
package org.alfresco.repo.action.executer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.action.ParameterDefinitionImpl;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.action.Action;
|
||||
import org.alfresco.service.cmr.action.ParameterDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
|
||||
/**
|
||||
* Add features action executor implementation.
|
||||
*
|
||||
* @author Roy Wetherall
|
||||
*/
|
||||
public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase
|
||||
{
|
||||
/**
|
||||
* Action constants
|
||||
*/
|
||||
public static final String NAME = "add-features";
|
||||
public static final String PARAM_ASPECT_NAME = "aspect-name";
|
||||
public static final String PARAM_CONSTRAINT = "ac-aspects";
|
||||
|
||||
/**
|
||||
* The node service
|
||||
*/
|
||||
private NodeService nodeService;
|
||||
|
||||
/** Transaction Service, used for retrying operations */
|
||||
private TransactionService transactionService;
|
||||
|
||||
/**
|
||||
* Set the node service
|
||||
*
|
||||
* @param nodeService the node service
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction service
|
||||
*
|
||||
* @param transactionService the transaction service
|
||||
*/
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adhoc properties are allowed for this executor
|
||||
*/
|
||||
@Override
|
||||
protected boolean getAdhocPropertiesAllowed()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,61 +96,55 @@ public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase
|
||||
*/
|
||||
public void executeImpl(final Action ruleAction, final NodeRef actionedUponNodeRef)
|
||||
{
|
||||
if (this.nodeService.exists(actionedUponNodeRef))
|
||||
{
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Void>() {
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
|
||||
QName aspectQName = null;
|
||||
|
||||
if (!nodeService.exists(actionedUponNodeRef))
|
||||
{
|
||||
// Node has gone away, skip
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build the aspect details
|
||||
Map<String, Serializable> paramValues = ruleAction.getParameterValues();
|
||||
removeActionContextParameter(paramValues);
|
||||
for (Map.Entry<String, Serializable> entry : paramValues.entrySet())
|
||||
{
|
||||
if (entry.getKey().equals(PARAM_ASPECT_NAME) == true)
|
||||
{
|
||||
aspectQName = (QName) entry.getValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must be an adhoc property
|
||||
QName propertyQName = QName.createQName(entry.getKey());
|
||||
Serializable propertyValue = entry.getValue();
|
||||
properties.put(propertyQName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the aspect
|
||||
nodeService.addAspect(actionedUponNodeRef, aspectQName, properties);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
|
||||
{
|
||||
paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT_NAME), false, "ac-aspects"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove actionContext from the parameter values to declassify as an adhoc property
|
||||
*/
|
||||
private void removeActionContextParameter(Map<String, Serializable> paramValues)
|
||||
{
|
||||
paramValues.remove(ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME);
|
||||
}
|
||||
}
|
||||
if (this.nodeService.exists(actionedUponNodeRef))
|
||||
{
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
|
||||
QName aspectQName = null;
|
||||
|
||||
if(! nodeService.exists(actionedUponNodeRef))
|
||||
{
|
||||
// Node has gone away, skip
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build the aspect details
|
||||
Map<String, Serializable> paramValues = ruleAction.getParameterValues();
|
||||
for (Map.Entry<String, Serializable> entry : paramValues.entrySet())
|
||||
{
|
||||
if (entry.getKey().equals(PARAM_ASPECT_NAME) == true)
|
||||
{
|
||||
aspectQName = (QName)entry.getValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must be an adhoc property
|
||||
QName propertyQName = QName.createQName(entry.getKey());
|
||||
Serializable propertyValue = entry.getValue();
|
||||
properties.put(propertyQName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the aspect
|
||||
nodeService.addAspect(actionedUponNodeRef, aspectQName, properties);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
|
||||
{
|
||||
paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT_NAME), false, "ac-aspects"));
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.audit;
|
||||
|
||||
|
@@ -21,7 +21,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.audit;
|
||||
|
||||
@@ -975,6 +975,8 @@ public class AuditComponentImpl implements AuditComponent
|
||||
@Override
|
||||
public int getAuditEntriesCountByAppAndProperties(String applicationName, AuditQueryParameters parameters)
|
||||
{
|
||||
org.alfresco.repo.domain.audit.AuditQueryParameters dbParameters = new org.alfresco.repo.domain.audit.AuditQueryParameters();
|
||||
|
||||
return auditDAO.getAuditEntriesCountByAppAndProperties(applicationName, parameters);
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.repo.domain;
|
||||
|
@@ -21,7 +21,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.domain.propval;
|
||||
|
||||
|
@@ -542,7 +542,10 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent applicationEvent)
|
||||
{
|
||||
// NOOP
|
||||
if (eventSender != null)
|
||||
{
|
||||
eventSender.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
protected class EventTransactionListener extends TransactionListenerAdapter
|
||||
|
@@ -52,7 +52,7 @@ public interface EventSender
|
||||
}
|
||||
|
||||
/**
|
||||
* It's called when the bean instance is destroyed, allowing to perform cleanup operations.
|
||||
* It's called when the application context is closing, allowing {@link org.alfresco.repo.event2.EventGenerator} to perform cleanup operations.
|
||||
*/
|
||||
default void destroy()
|
||||
{
|
||||
|
@@ -25,16 +25,15 @@
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
import jakarta.annotation.Nonnull;
|
||||
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
|
||||
{
|
||||
@@ -52,7 +51,7 @@ public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
|
||||
private boolean legacySkipQueueConfig;
|
||||
|
||||
public EventSenderFactoryBean(@Autowired PropertyResolver propertyResolver, Event2MessageProducer event2MessageProducer,
|
||||
Executor enqueueThreadPoolExecutor, Executor dequeueThreadPoolExecutor)
|
||||
Executor enqueueThreadPoolExecutor, Executor dequeueThreadPoolExecutor)
|
||||
{
|
||||
super();
|
||||
PropertyCheck.mandatory(this, "propertyResolver", propertyResolver);
|
||||
@@ -156,13 +155,4 @@ public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
|
||||
{
|
||||
return event2MessageProducer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroyInstance(EventSender eventSender)
|
||||
{
|
||||
if (eventSender != null)
|
||||
{
|
||||
eventSender.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -37,7 +37,6 @@ import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.transform.config.CoreFunction;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -47,7 +46,6 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import static org.alfresco.model.ContentModel.PROP_CONTENT;
|
||||
import static org.alfresco.transform.common.RequestParamMap.DIRECT_ACCESS_URL;
|
||||
@@ -70,7 +68,6 @@ public class LocalTransformClient implements TransformClient, InitializingBean
|
||||
private ContentService contentService;
|
||||
private RenditionService2Impl renditionService2;
|
||||
private boolean directAccessUrlEnabled;
|
||||
private int threadPoolSize;
|
||||
|
||||
private ExecutorService executorService;
|
||||
private ThreadLocal<LocalTransform> transform = new ThreadLocal<>();
|
||||
@@ -100,11 +97,6 @@ public class LocalTransformClient implements TransformClient, InitializingBean
|
||||
this.directAccessUrlEnabled = directAccessUrlEnabled;
|
||||
}
|
||||
|
||||
public void setThreadPoolSize(int threadPoolSize)
|
||||
{
|
||||
this.threadPoolSize = threadPoolSize;
|
||||
}
|
||||
|
||||
public void setExecutorService(ExecutorService executorService)
|
||||
{
|
||||
this.executorService = executorService;
|
||||
@@ -118,11 +110,9 @@ public class LocalTransformClient implements TransformClient, InitializingBean
|
||||
PropertyCheck.mandatory(this, "contentService", contentService);
|
||||
PropertyCheck.mandatory(this, "renditionService2", renditionService2);
|
||||
PropertyCheck.mandatory(this, "directAccessUrlEnabled", directAccessUrlEnabled);
|
||||
PropertyCheck.mandatory(this, "threadPoolSize", threadPoolSize);
|
||||
if (executorService == null)
|
||||
{
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("local-transform-%d").build();
|
||||
executorService = Executors.newFixedThreadPool(threadPoolSize, threadFactory);
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,27 +25,35 @@
|
||||
*/
|
||||
package org.alfresco.repo.rendition2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.service.NotAuditable;
|
||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Async Rendition service. Replaces the original rendition services which included synchronous renditions and asynchronous methods with Java call backs.
|
||||
* <p/>
|
||||
* The Async Rendition service. Replaces the original rendition services which included synchronous renditions and
|
||||
* asynchronous methods with Java call backs.<p/>
|
||||
*
|
||||
* Renditions are defined as {@link RenditionDefinition2}s and may be registered and looked by the associated {@link RenditionDefinitionRegistry2}.
|
||||
* <p/>
|
||||
* Renditions are defined as {@link RenditionDefinition2}s and may be registered and looked by the associated
|
||||
* {@link RenditionDefinitionRegistry2}.<p/>
|
||||
*
|
||||
* Unlike the original RenditionService this service, it:
|
||||
* <ul>
|
||||
* <li>Performs async renditions without a Java callback, as another node in the cluster may complete the rendition. The current node requests a transform, but another node might take the resulting transform and turn it into a rendition if the external Transform Service is used.</li>
|
||||
* <li>Reduces the configurable options to do with with the associations of rendition nodes, their type. They are identical to 'hidden' (not normally seen as nodes in their own right in a UI) renditions produced by the original service. So, they are always directly under the source node connected by a {@code}rn:rendition{@code} association with the name of the rendition.</li>
|
||||
* <li>The rendition nodes additionally have a {@code}rn:rendition2{@code} aspect and a {@code}contentUrlHashCode{@code} property. This property contains a value that allows the service to work out if it holds a rendition of the source node's current content.</li>
|
||||
* <li>Failures are handled by setting the rendition node's content to null.</li>
|
||||
* <li>When a rendition is requested via the REST API, only the newer service is used.</li>
|
||||
* <li>Where possible old service renditions migrate automatically over to the new service when content on a source node is updated.</li>
|
||||
* <li>Performs async renditions without a Java callback, as another node in the cluster may complete the rendition.
|
||||
* The current node requests a transform, but another node might take the resulting transform and turn it into a
|
||||
* rendition if the external Transform Service is used.</li>
|
||||
* <li>Reduces the configurable options to do with with the associations of rendition nodes, their type. They
|
||||
* are identical to 'hidden' (not normally seen as nodes in their own right in a
|
||||
* UI) renditions produced by the original service. So, they are always directly under the source node connected by a
|
||||
* {@code}rn:rendition{@code} association with the name of the rendition.</li>
|
||||
* <li>The rendition nodes additionally have a {@code}rn:rendition2{@code} aspect and a {@code}contentUrlHashCode{@code}
|
||||
* property. This property contains a value that allows the service to work out if it holds a rendition of the
|
||||
* source node's current content.</li>
|
||||
* <li>Failures are handled by setting the rendition node's content to null.</li>
|
||||
* <li>When a rendition is requested via the REST API, only the newer service is used.</li>
|
||||
* <li>Where possible old service renditions migrate automatically over to the new service when content on a
|
||||
* source node is updated.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author adavis
|
||||
@@ -58,30 +66,29 @@ public interface RenditionService2
|
||||
RenditionDefinitionRegistry2 getRenditionDefinitionRegistry2();
|
||||
|
||||
/**
|
||||
* This method asynchronously transforms content to a target mimetype with transform options supplied in the {@code transformDefinition}. A response is set on a message queue once the transform is complete or fails, together with some client supplied data. The response queue and client data are also included in the transformDefinition.
|
||||
* <p>
|
||||
* This method asynchronously transforms content to a target mimetype with transform options supplied in the
|
||||
* {@code transformDefinition}. A response is set on a message queue once the transform is complete or fails,
|
||||
* together with some client supplied data. The response queue and client data are also included in the
|
||||
* transformDefinition.<p>
|
||||
*
|
||||
* This method does not create a rendition node, but uses the same code as renditions to perform the transform. The {@code transformDefinition} extends {@link RenditionDefinition2}, but is not stored in a {@link RenditionDefinitionRegistry2}, as it is transient in nature.
|
||||
* This method does not create a rendition node, but uses the same code as renditions to perform the transform. The
|
||||
* {@code transformDefinition} extends {@link RenditionDefinition2}, but is not stored in a
|
||||
* {@link RenditionDefinitionRegistry2}, as it is transient in nature.
|
||||
*
|
||||
* @param sourceNodeRef
|
||||
* the node from which the content is retrieved.
|
||||
* @param transformDefinition
|
||||
* which defines the transform, where to sent the response and some client specified data.
|
||||
* @throws UnsupportedOperationException
|
||||
* if the transform is not supported.
|
||||
* @param sourceNodeRef the node from which the content is retrieved.
|
||||
* @param transformDefinition which defines the transform, where to sent the response and some client specified data.
|
||||
* @throws UnsupportedOperationException if the transform is not supported.
|
||||
*/
|
||||
@NotAuditable
|
||||
public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition);
|
||||
|
||||
/**
|
||||
* This method asynchronously renders content as specified by the {@code renditionName}. The content to be rendered is provided by {@code sourceNodeRef}.
|
||||
* This method asynchronously renders content as specified by the {@code renditionName}. The content to be
|
||||
* rendered is provided by {@code sourceNodeRef}.
|
||||
*
|
||||
* @param sourceNodeRef
|
||||
* the node from which the content is retrieved.
|
||||
* @param renditionName
|
||||
* the rendition to be performed.
|
||||
* @throws UnsupportedOperationException
|
||||
* if the transform is not supported AND the rendition has not been created before.
|
||||
* @param sourceNodeRef the node from which the content is retrieved.
|
||||
* @param renditionName the rendition to be performed.
|
||||
* @throws UnsupportedOperationException if the transform is not supported AND the rendition has not been created before.
|
||||
*/
|
||||
@NotAuditable
|
||||
public void render(NodeRef sourceNodeRef, String renditionName);
|
||||
@@ -97,11 +104,10 @@ public interface RenditionService2
|
||||
/**
|
||||
* This method gets the rendition of the {@code sourceNodeRef} identified by its name.
|
||||
*
|
||||
* @param sourceNodeRef
|
||||
* the source node for the renditions
|
||||
* @param renditionName
|
||||
* the renditionName used to identify a rendition.
|
||||
* @return the {@link ChildAssociationRef} which links the source node to the rendition or <code>null</code> if there is no rendition or it is not up to date.
|
||||
* @param sourceNodeRef the source node for the renditions
|
||||
* @param renditionName the renditionName used to identify a rendition.
|
||||
* @return the {@link ChildAssociationRef} which links the source node to the
|
||||
* rendition or <code>null</code> if there is no rendition or it is not up to date.
|
||||
*/
|
||||
@NotAuditable
|
||||
ChildAssociationRef getRenditionByName(NodeRef sourceNodeRef, String renditionName);
|
||||
@@ -109,8 +115,7 @@ public interface RenditionService2
|
||||
/**
|
||||
* This method clears source nodeRef rendition content and content hash code using supplied rendition name.
|
||||
*
|
||||
* @param renditionNode
|
||||
* the rendition node
|
||||
* @param renditionNode the rendition node
|
||||
*/
|
||||
@NotAuditable
|
||||
void clearRenditionContentDataInTransaction(NodeRef renditionNode);
|
||||
@@ -119,13 +124,4 @@ public interface RenditionService2
|
||||
* Indicates if renditions are enabled. Set using the {@code system.thumbnail.generate} value.
|
||||
*/
|
||||
boolean isEnabled();
|
||||
|
||||
/**
|
||||
* This method forces the content hash code for every {@code sourceNodeRef} renditions.
|
||||
*
|
||||
* @param sourceNodeRef
|
||||
* the source node to update renditions hash code
|
||||
*/
|
||||
@NotAuditable
|
||||
void forceRenditionsContentHashCode(NodeRef sourceNodeRef);
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,24 +25,6 @@
|
||||
*/
|
||||
package org.alfresco.repo.rendition2;
|
||||
|
||||
import static org.alfresco.model.ContentModel.PROP_CONTENT;
|
||||
import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE;
|
||||
import static org.alfresco.service.namespace.QName.createQName;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.model.RenditionModel;
|
||||
import org.alfresco.repo.content.ContentServicePolicies;
|
||||
@@ -69,6 +51,23 @@ import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.alfresco.model.ContentModel.PROP_CONTENT;
|
||||
import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE;
|
||||
import static org.alfresco.service.namespace.QName.createQName;
|
||||
|
||||
/**
|
||||
* The Async Rendition service. Replaces the original deprecated RenditionService.
|
||||
@@ -96,10 +95,12 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
abstract RenditionDefinition2 getRenditionDefinition();
|
||||
|
||||
void handleUnsupported(UnsupportedOperationException e)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
void throwIllegalStateExceptionIfAlreadyDone(int sourceContentHashCode)
|
||||
{}
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private TransactionService transactionService;
|
||||
@@ -216,7 +217,8 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
@Override
|
||||
public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition)
|
||||
{
|
||||
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() {
|
||||
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
@@ -235,7 +237,8 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
@Override
|
||||
public void render(NodeRef sourceNodeRef, String renditionName)
|
||||
{
|
||||
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() {
|
||||
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
@@ -258,7 +261,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
@Override
|
||||
public void handleUnsupported(UnsupportedOperationException e)
|
||||
{
|
||||
// On the initial request for a rendition throw the exception.
|
||||
// On the initial request for a rendition throw the exception.
|
||||
NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
|
||||
if (renditionNode == null)
|
||||
{
|
||||
@@ -274,7 +277,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode + " hashCodes");
|
||||
logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode+ " hashCodes");
|
||||
}
|
||||
if (renditionContentHashCode == sourceContentHashCode)
|
||||
{
|
||||
@@ -296,14 +299,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
|
||||
if (!nodeService.exists(sourceNodeRef))
|
||||
{
|
||||
throw new IllegalArgumentException(renderOrTransform.getName() + ": The supplied sourceNodeRef " + sourceNodeRef + " does not exist.");
|
||||
throw new IllegalArgumentException(renderOrTransform.getName()+ ": The supplied sourceNodeRef "+sourceNodeRef+" does not exist.");
|
||||
}
|
||||
|
||||
RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition();
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(renderOrTransform.getName() + ": transform " + sourceNodeRef);
|
||||
logger.debug(renderOrTransform.getName()+ ": transform " +sourceNodeRef);
|
||||
}
|
||||
|
||||
AtomicBoolean supported = new AtomicBoolean(true);
|
||||
@@ -325,13 +328,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
String user = AuthenticationUtil.getRunAsUser();
|
||||
RetryingTransactionHelper.RetryingTransactionCallback callback = () -> {
|
||||
RetryingTransactionHelper.RetryingTransactionCallback callback = () ->
|
||||
{
|
||||
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
|
||||
if (!supported.get())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(renderOrTransform.getName() + " is not supported. " +
|
||||
logger.debug(renderOrTransform.getName() +" is not supported. " +
|
||||
"The content might be too big or the source mimetype cannot be converted.");
|
||||
}
|
||||
failure(sourceNodeRef, renditionDefinition, sourceContentHashCode);
|
||||
@@ -368,24 +372,26 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
public void failure(NodeRef sourceNodeRef, RenditionDefinition2 renditionDefinition, int transformContentHashCode)
|
||||
{
|
||||
// The original transaction may have already have failed
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
|
||||
consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode);
|
||||
return null;
|
||||
}, false, true));
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () ->
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(() ->
|
||||
{
|
||||
consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode);
|
||||
return null;
|
||||
}, false, true));
|
||||
}
|
||||
|
||||
public void consume(NodeRef sourceNodeRef, InputStream transformInputStream, RenditionDefinition2 renditionDefinition,
|
||||
int transformContentHashCode)
|
||||
int transformContentHashCode)
|
||||
{
|
||||
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Consume: Source " + sourceContentHashCode + " and transform's source " + transformContentHashCode + " hashcodes");
|
||||
logger.debug("Consume: Source " + sourceContentHashCode + " and transform's source " + transformContentHashCode+" hashcodes");
|
||||
}
|
||||
|
||||
if (renditionDefinition instanceof TransformDefinition)
|
||||
{
|
||||
TransformDefinition transformDefinition = (TransformDefinition) renditionDefinition;
|
||||
TransformDefinition transformDefinition = (TransformDefinition)renditionDefinition;
|
||||
String targetMimetype = transformDefinition.getTargetMimetype();
|
||||
if (AsynchronousExtractor.isMetadataExtractMimetype(targetMimetype))
|
||||
{
|
||||
@@ -407,7 +413,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
private void consumeExtractedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream,
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
{
|
||||
if (transformInputStream == null)
|
||||
{
|
||||
@@ -434,7 +440,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
private void consumeEmbeddedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream,
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
{
|
||||
if (transformInputStream == null)
|
||||
{
|
||||
@@ -462,7 +468,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
private void consumeTransformReply(NodeRef sourceNodeRef, InputStream transformInputStream,
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
@@ -478,10 +484,12 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a transformation (InputStream) and attaches it as a rendition to the source node. Does nothing if there is already a newer rendition. If the transformInputStream is null, this is taken to be a transform failure.
|
||||
* Takes a transformation (InputStream) and attaches it as a rendition to the source node.
|
||||
* Does nothing if there is already a newer rendition.
|
||||
* If the transformInputStream is null, this is taken to be a transform failure.
|
||||
*/
|
||||
private void consumeRendition(NodeRef sourceNodeRef, int sourceContentHashCode, InputStream transformInputStream,
|
||||
RenditionDefinition2 renditionDefinition, int transformContentHashCode)
|
||||
RenditionDefinition2 renditionDefinition, int transformContentHashCode)
|
||||
{
|
||||
String renditionName = renditionDefinition.getRenditionName();
|
||||
if (transformContentHashCode != sourceContentHashCode)
|
||||
@@ -499,92 +507,93 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
(transformInputStream == null ? " to null as the transform failed" : " to the transform result"));
|
||||
}
|
||||
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
|
||||
// Ensure that the creation of a rendition does not cause updates to the modified, modifier properties on the source node
|
||||
NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
|
||||
boolean createRenditionNode = renditionNode == null;
|
||||
boolean sourceHasAspectRenditioned = nodeService.hasAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED);
|
||||
try
|
||||
{
|
||||
ruleService.disableRuleType(RuleType.UPDATE);
|
||||
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
|
||||
// If they do not exist create the rendition association and the rendition node.
|
||||
if (createRenditionNode)
|
||||
{
|
||||
renditionNode = createRenditionNode(sourceNodeRef, renditionDefinition);
|
||||
}
|
||||
else if (!nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
{
|
||||
nodeService.addAspect(renditionNode, RenditionModel.ASPECT_RENDITION2, null);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Added rendition2 aspect to rendition " + renditionName + " on " + sourceNodeRef);
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Set ThumbnailLastModified for " + renditionName);
|
||||
}
|
||||
setThumbnailLastModified(sourceNodeRef, renditionName);
|
||||
|
||||
if (transformInputStream != null)
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () ->
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(() ->
|
||||
{
|
||||
// Ensure that the creation of a rendition does not cause updates to the modified, modifier properties on the source node
|
||||
NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
|
||||
boolean createRenditionNode = renditionNode == null;
|
||||
boolean sourceHasAspectRenditioned = nodeService.hasAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED);
|
||||
try
|
||||
{
|
||||
// Set or replace rendition content
|
||||
ContentWriter contentWriter = contentService.getWriter(renditionNode, DEFAULT_RENDITION_CONTENT_PROP, true);
|
||||
String targetMimetype = renditionDefinition.getTargetMimetype();
|
||||
contentWriter.setMimetype(targetMimetype);
|
||||
contentWriter.setEncoding(DEFAULT_ENCODING);
|
||||
ContentWriter renditionWriter = contentWriter;
|
||||
renditionWriter.putContent(transformInputStream);
|
||||
ruleService.disableRuleType(RuleType.UPDATE);
|
||||
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
|
||||
ContentReader contentReader = renditionWriter.getReader();
|
||||
long sizeOfRendition = contentReader.getSize();
|
||||
if (sizeOfRendition > 0L)
|
||||
// If they do not exist create the rendition association and the rendition node.
|
||||
if (createRenditionNode)
|
||||
{
|
||||
renditionNode = createRenditionNode(sourceNodeRef, renditionDefinition);
|
||||
}
|
||||
else if (!nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
{
|
||||
nodeService.addAspect(renditionNode, RenditionModel.ASPECT_RENDITION2, null);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Set rendition hashcode for " + renditionName);
|
||||
logger.debug("Added rendition2 aspect to rendition " + renditionName + " on " + sourceNodeRef);
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Set ThumbnailLastModified for " + renditionName);
|
||||
}
|
||||
setThumbnailLastModified(sourceNodeRef, renditionName);
|
||||
|
||||
if (transformInputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set or replace rendition content
|
||||
ContentWriter contentWriter = contentService.getWriter(renditionNode, DEFAULT_RENDITION_CONTENT_PROP, true);
|
||||
String targetMimetype = renditionDefinition.getTargetMimetype();
|
||||
contentWriter.setMimetype(targetMimetype);
|
||||
contentWriter.setEncoding(DEFAULT_ENCODING);
|
||||
ContentWriter renditionWriter = contentWriter;
|
||||
renditionWriter.putContent(transformInputStream);
|
||||
|
||||
ContentReader contentReader = renditionWriter.getReader();
|
||||
long sizeOfRendition = contentReader.getSize();
|
||||
if (sizeOfRendition > 0L)
|
||||
{
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Set rendition hashcode for " + renditionName);
|
||||
}
|
||||
nodeService.setProperty(renditionNode, RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE, transformContentHashCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error("Transform was zero bytes for " + renditionName + " on " + sourceNodeRef);
|
||||
clearRenditionContentData(renditionNode);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.error("Failed to copy transform InputStream into rendition " + renditionName + " on " + sourceNodeRef);
|
||||
throw e;
|
||||
}
|
||||
nodeService.setProperty(renditionNode, RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE, transformContentHashCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error("Transform was zero bytes for " + renditionName + " on " + sourceNodeRef);
|
||||
clearRenditionContentData(renditionNode);
|
||||
}
|
||||
|
||||
if (!sourceHasAspectRenditioned)
|
||||
{
|
||||
nodeService.addAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED, null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.error("Failed to copy transform InputStream into rendition " + renditionName + " on " + sourceNodeRef);
|
||||
throw e;
|
||||
throw new RenditionService2Exception(TRANSFORMING_ERROR_MESSAGE + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clearRenditionContentData(renditionNode);
|
||||
}
|
||||
|
||||
if (!sourceHasAspectRenditioned)
|
||||
{
|
||||
nodeService.addAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED, null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RenditionService2Exception(TRANSFORMING_ERROR_MESSAGE + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
ruleService.enableRuleType(RuleType.UPDATE);
|
||||
}
|
||||
return null;
|
||||
}, false, true));
|
||||
finally
|
||||
{
|
||||
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
ruleService.enableRuleType(RuleType.UPDATE);
|
||||
}
|
||||
return null;
|
||||
}, false, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,14 +634,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("Setting thumbnail last modified date to " + lastModifiedValue + " on source node: " + sourceNodeRef);
|
||||
logger.trace("Setting thumbnail last modified date to " + lastModifiedValue +" on source node: " + sourceNodeRef);
|
||||
}
|
||||
|
||||
if (nodeService.hasAspect(sourceNodeRef, ContentModel.ASPECT_THUMBNAIL_MODIFICATION))
|
||||
{
|
||||
List<String> thumbnailMods = (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA);
|
||||
String target = null;
|
||||
for (String currThumbnailMod : thumbnailMods)
|
||||
for (String currThumbnailMod: thumbnailMods)
|
||||
{
|
||||
if (currThumbnailMod.startsWith(prefix))
|
||||
{
|
||||
@@ -656,7 +665,8 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code of the source node's content url. As transformations may be returned in a different sequences to which they were requested, this is used work out if a rendition should be replaced.
|
||||
* Returns the hash code of the source node's content url. As transformations may be returned in a different
|
||||
* sequences to which they were requested, this is used work out if a rendition should be replaced.
|
||||
*/
|
||||
private int getSourceContentHashCode(NodeRef sourceNodeRef)
|
||||
{
|
||||
@@ -665,7 +675,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
if (contentData != null)
|
||||
{
|
||||
// Originally we used the contentData URL, but that is not enough if the mimetype changes.
|
||||
String contentString = contentData.getContentUrl() + contentData.getMimetype();
|
||||
String contentString = contentData.getContentUrl()+contentData.getMimetype();
|
||||
if (contentString != null)
|
||||
{
|
||||
hashCode = contentString.hashCode();
|
||||
@@ -675,11 +685,13 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code of source node's content url on the rendition node (node may be null) if it does not exist. Used work out if a rendition should be replaced. {@code -2} is returned if the rendition does not exist or was not created by RenditionService2. {@code -1} is returned if there was no source content or the rendition failed.
|
||||
* Returns the hash code of source node's content url on the rendition node (node may be null) if it does not exist.
|
||||
* Used work out if a rendition should be replaced. {@code -2} is returned if the rendition does not exist or was
|
||||
* not created by RenditionService2. {@code -1} is returned if there was no source content or the rendition failed.
|
||||
*/
|
||||
private int getRenditionContentHashCode(NodeRef renditionNode)
|
||||
{
|
||||
if (renditionNode == null || !nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
if ( renditionNode == null || !nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
{
|
||||
return RENDITION2_DOES_NOT_EXIST;
|
||||
}
|
||||
@@ -687,7 +699,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
Serializable hashCode = nodeService.getProperty(renditionNode, PROP_RENDITION_CONTENT_HASH_CODE);
|
||||
return hashCode == null
|
||||
? SOURCE_HAS_NO_CONTENT
|
||||
: (int) hashCode;
|
||||
: (int)hashCode;
|
||||
}
|
||||
|
||||
private NodeRef getRenditionNode(NodeRef sourceNodeRef, String renditionName)
|
||||
@@ -761,12 +773,11 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks whether the specified source node is of a content class which has been registered for rendition prevention.
|
||||
* This method checks whether the specified source node is of a content class which has been registered for
|
||||
* rendition prevention.
|
||||
*
|
||||
* @param sourceNode
|
||||
* the node to check.
|
||||
* @throws RenditionService2PreventedException
|
||||
* if the source node is configured for rendition prevention.
|
||||
* @param sourceNode the node to check.
|
||||
* @throws RenditionService2PreventedException if the source node is configured for rendition prevention.
|
||||
*/
|
||||
// This code is based on the old RenditionServiceImpl.checkSourceNodeForPreventionClass(...)
|
||||
private void checkSourceNodeForPreventionClass(NodeRef sourceNode)
|
||||
@@ -812,7 +823,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
|
||||
for (ChildAssociationRef childAssoc : childAsocs)
|
||||
{
|
||||
NodeRef renditionNode = childAssoc.getChildRef();
|
||||
NodeRef renditionNode = childAssoc.getChildRef();
|
||||
if (isRenditionAvailable(sourceNodeRef, renditionNode))
|
||||
{
|
||||
result.add(childAssoc);
|
||||
@@ -822,7 +833,8 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the rendition is available. Failed renditions (there was an error) don't have a contentUrl and out of date renditions or those still being created don't have a matching contentHashCode.
|
||||
* Indicates if the rendition is available. Failed renditions (there was an error) don't have a contentUrl
|
||||
* and out of date renditions or those still being created don't have a matching contentHashCode.
|
||||
*/
|
||||
public boolean isRenditionAvailable(NodeRef sourceNodeRef, NodeRef renditionNode)
|
||||
{
|
||||
@@ -840,7 +852,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode + " hashcodes");
|
||||
logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode+" hashcodes");
|
||||
}
|
||||
if (sourceContentHashCode != renditionContentHashCode)
|
||||
{
|
||||
@@ -880,17 +892,19 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
ChildAssociationRef childAssoc = renditions.get(0);
|
||||
NodeRef renditionNode = childAssoc.getChildRef();
|
||||
return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null : childAssoc;
|
||||
return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null: childAssoc;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearRenditionContentDataInTransaction(NodeRef renditionNode)
|
||||
{
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
|
||||
clearRenditionContentData(renditionNode);
|
||||
return null;
|
||||
}, false, true));
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () ->
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(() ->
|
||||
{
|
||||
clearRenditionContentData(renditionNode);
|
||||
return null;
|
||||
}, false, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -936,35 +950,4 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceRenditionsContentHashCode(NodeRef sourceNodeRef)
|
||||
{
|
||||
if (sourceNodeRef != null && nodeService.exists(sourceNodeRef))
|
||||
{
|
||||
List<ChildAssociationRef> renditions = getRenditionChildAssociations(sourceNodeRef);
|
||||
if (renditions != null)
|
||||
{
|
||||
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
|
||||
for (ChildAssociationRef rendition : renditions)
|
||||
{
|
||||
NodeRef renditionNode = rendition.getChildRef();
|
||||
if (nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
{
|
||||
int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
|
||||
String renditionName = rendition.getQName().getLocalName();
|
||||
if (sourceContentHashCode != renditionContentHashCode)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Update content hash code for rendition " + renditionName + " of node "
|
||||
+ sourceNodeRef);
|
||||
}
|
||||
nodeService.setProperty(renditionNode, PROP_RENDITION_CONTENT_HASH_CODE,
|
||||
sourceContentHashCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -29,17 +29,28 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* An interface for objects capable of extracting an externally authenticated user ID from the HTTP request.
|
||||
* An interface for objects capable of extracting an externally authenticated user ID from the HTTP Admin Console webscript request.
|
||||
*/
|
||||
public interface ExternalUserAuthenticator
|
||||
public interface AdminConsoleAuthenticator
|
||||
{
|
||||
/**
|
||||
* Gets an externally authenticated user ID from the HTTP request.
|
||||
* Gets an externally authenticated user ID from the HTTP Admin Console webscript request.
|
||||
*
|
||||
* @param request
|
||||
* the request
|
||||
* @param response
|
||||
* the response
|
||||
* @return the user ID or <code>null</code> if the user is unauthenticated
|
||||
*/
|
||||
String getUserId(HttpServletRequest request, HttpServletResponse response);
|
||||
String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response);
|
||||
|
||||
/* Sends redirect to external site to initiate the OIDC authorization code flow. */
|
||||
/**
|
||||
* Requests an authentication.
|
||||
*
|
||||
* @param request
|
||||
* the request
|
||||
* @param response
|
||||
* the response
|
||||
*/
|
||||
void requestAuthentication(HttpServletRequest request, HttpServletResponse response);
|
||||
}
|
@@ -30,12 +30,12 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
|
||||
/**
|
||||
* A default {@link ExternalUserAuthenticator} implementation. Returns null to request a basic auth challenge.
|
||||
* A default {@link AdminConsoleAuthenticator} implementation. Returns null to request a basic auth challenge.
|
||||
*/
|
||||
public class DefaultAdminConsoleAuthenticator implements ExternalUserAuthenticator, ActivateableBean
|
||||
public class DefaultAdminConsoleAuthenticator implements AdminConsoleAuthenticator, ActivateableBean
|
||||
{
|
||||
@Override
|
||||
public String getUserId(HttpServletRequest request, HttpServletResponse response)
|
||||
public String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.external;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
|
||||
/**
|
||||
* A default {@link ExternalUserAuthenticator} implementation. Returns null to request a basic auth challenge.
|
||||
*/
|
||||
public class DefaultWebScriptsHomeAuthenticator implements ExternalUserAuthenticator, ActivateableBean
|
||||
{
|
||||
@Override
|
||||
public String getUserId(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
// No implementation
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -1,121 +1,123 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.OIDCUserInfo;
|
||||
|
||||
/**
|
||||
*
|
||||
* Authenticates a user against Identity Service (Keycloak/Authorization Server). {@link IdentityServiceFacade} is used to verify provided user credentials. User is set as the current user if the user credentials are valid. <br>
|
||||
* The {@link IdentityServiceAuthenticationComponent#identityServiceFacade} can be null in which case this authenticator will just fall through to the next one in the chain.
|
||||
*
|
||||
*/
|
||||
public class IdentityServiceAuthenticationComponent extends AbstractAuthenticationComponent implements ActivateableBean
|
||||
{
|
||||
private final Log LOGGER = LogFactory.getLog(IdentityServiceAuthenticationComponent.class);
|
||||
/** client used to authenticate user credentials against Authorization Server **/
|
||||
private IdentityServiceFacade identityServiceFacade;
|
||||
/** enabled flag for the identity service subsystem **/
|
||||
private boolean active;
|
||||
|
||||
private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
|
||||
|
||||
private boolean allowGuestLogin;
|
||||
|
||||
public void setIdentityServiceFacade(IdentityServiceFacade identityServiceFacade)
|
||||
{
|
||||
this.identityServiceFacade = identityServiceFacade;
|
||||
}
|
||||
|
||||
public void setAllowGuestLogin(boolean allowGuestLogin)
|
||||
{
|
||||
this.allowGuestLogin = allowGuestLogin;
|
||||
}
|
||||
|
||||
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
|
||||
{
|
||||
this.jitProvisioningHandler = jitProvisioningHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticateImpl(String userName, char[] password) throws AuthenticationException
|
||||
{
|
||||
if (identityServiceFacade == null)
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("IdentityServiceFacade was not set, possibly due to the 'identity-service.authentication.enable-username-password-authentication=false' property.");
|
||||
}
|
||||
|
||||
throw new AuthenticationException("User not authenticated because IdentityServiceFacade was not set.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt to verify user credentials
|
||||
IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(AuthorizationGrant.password(userName, String.valueOf(password)));
|
||||
|
||||
String normalizedUsername = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(accessTokenAuthorization.getAccessToken().getTokenValue())
|
||||
.map(OIDCUserInfo::username)
|
||||
.orElseThrow(() -> new AuthenticationException("Failed to extract username from token and user info endpoint."));
|
||||
// Verification was successful so treat as authenticated user
|
||||
setCurrentUser(normalizedUsername);
|
||||
}
|
||||
catch (IdentityServiceFacadeException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to verify user credentials against the OAuth2 Authorization Server.", e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to verify user credentials.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setActive(boolean active)
|
||||
{
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean implementationAllowsGuestLogin()
|
||||
{
|
||||
return allowGuestLogin;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Authenticates a user against Identity Service (Keycloak/Authorization Server).
|
||||
* {@link IdentityServiceFacade} is used to verify provided user credentials. User is set as the current user if the
|
||||
* user credentials are valid.
|
||||
* <br>
|
||||
* The {@link IdentityServiceAuthenticationComponent#identityServiceFacade} can be null in which case this authenticator
|
||||
* will just fall through to the next one in the chain.
|
||||
*
|
||||
*/
|
||||
public class IdentityServiceAuthenticationComponent extends AbstractAuthenticationComponent implements ActivateableBean
|
||||
{
|
||||
private final Log LOGGER = LogFactory.getLog(IdentityServiceAuthenticationComponent.class);
|
||||
/** client used to authenticate user credentials against Authorization Server **/
|
||||
private IdentityServiceFacade identityServiceFacade;
|
||||
/** enabled flag for the identity service subsystem**/
|
||||
private boolean active;
|
||||
|
||||
private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
|
||||
|
||||
private boolean allowGuestLogin;
|
||||
|
||||
public void setIdentityServiceFacade(IdentityServiceFacade identityServiceFacade)
|
||||
{
|
||||
this.identityServiceFacade = identityServiceFacade;
|
||||
}
|
||||
|
||||
public void setAllowGuestLogin(boolean allowGuestLogin)
|
||||
{
|
||||
this.allowGuestLogin = allowGuestLogin;
|
||||
}
|
||||
|
||||
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
|
||||
{
|
||||
this.jitProvisioningHandler = jitProvisioningHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticateImpl(String userName, char[] password) throws AuthenticationException
|
||||
{
|
||||
if (identityServiceFacade == null)
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("IdentityServiceFacade was not set, possibly due to the 'identity-service.authentication.enable-username-password-authentication=false' property.");
|
||||
}
|
||||
|
||||
throw new AuthenticationException("User not authenticated because IdentityServiceFacade was not set.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt to verify user credentials
|
||||
IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(AuthorizationGrant.password(userName, String.valueOf(password)));
|
||||
|
||||
String normalizedUsername = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(accessTokenAuthorization.getAccessToken().getTokenValue())
|
||||
.map(OIDCUserInfo::username)
|
||||
.orElseThrow(() -> new AuthenticationException("Failed to extract username from token and user info endpoint."));
|
||||
// Verification was successful so treat as authenticated user
|
||||
setCurrentUser(normalizedUsername);
|
||||
}
|
||||
catch (IdentityServiceFacadeException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to verify user credentials against the OAuth2 Authorization Server.", e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to verify user credentials.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setActive(boolean active)
|
||||
{
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean implementationAllowsGuestLogin()
|
||||
{
|
||||
return allowGuestLogin;
|
||||
}
|
||||
}
|
||||
|
@@ -1,437 +1,330 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Class to hold configuration for the Identity Service.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
@SuppressWarnings("PMD.ExcessivePublicCount")
|
||||
public class IdentityServiceConfig
|
||||
{
|
||||
private static final String REALMS = "realms";
|
||||
|
||||
private int clientConnectionTimeout;
|
||||
private int clientSocketTimeout;
|
||||
private String issuerUrl;
|
||||
private String audience;
|
||||
// client id
|
||||
private String resource;
|
||||
private String clientSecret;
|
||||
private String authServerUrl;
|
||||
private String realm;
|
||||
private int connectionPoolSize;
|
||||
private boolean allowAnyHostname;
|
||||
private boolean disableTrustManager;
|
||||
private String truststore;
|
||||
private String truststorePassword;
|
||||
private String clientKeystore;
|
||||
private String clientKeystorePassword;
|
||||
private String clientKeyPassword;
|
||||
private String realmKey;
|
||||
private int publicKeyCacheTtl;
|
||||
private boolean publicClient;
|
||||
private String principalAttribute;
|
||||
private boolean clientIdValidationDisabled;
|
||||
private String adminConsoleRedirectPath;
|
||||
private String signatureAlgorithms;
|
||||
private String adminConsoleScopes;
|
||||
private String passwordGrantScopes;
|
||||
private String issuerAttribute;
|
||||
private String firstNameAttribute;
|
||||
private String lastNameAttribute;
|
||||
private String emailAttribute;
|
||||
private long jwtClockSkewMs;
|
||||
private String webScriptsHomeRedirectPath;
|
||||
private String webScriptsHomeScopes;
|
||||
|
||||
public String getWebScriptsHomeRedirectPath()
|
||||
{
|
||||
return webScriptsHomeRedirectPath;
|
||||
}
|
||||
|
||||
public void setWebScriptsHomeRedirectPath(String webScriptsHomeRedirectPath)
|
||||
{
|
||||
this.webScriptsHomeRedirectPath = webScriptsHomeRedirectPath;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Client connection timeout in milliseconds.
|
||||
*/
|
||||
public int getClientConnectionTimeout()
|
||||
{
|
||||
return clientConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientConnectionTimeout
|
||||
* Client connection timeout in milliseconds.
|
||||
*/
|
||||
public void setClientConnectionTimeout(int clientConnectionTimeout)
|
||||
{
|
||||
this.clientConnectionTimeout = clientConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Client socket timeout in milliseconds.s
|
||||
*/
|
||||
public int getClientSocketTimeout()
|
||||
{
|
||||
return clientSocketTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientSocketTimeout
|
||||
* Client socket timeout in milliseconds.
|
||||
*/
|
||||
public void setClientSocketTimeout(int clientSocketTimeout)
|
||||
{
|
||||
this.clientSocketTimeout = clientSocketTimeout;
|
||||
}
|
||||
|
||||
public void setConnectionPoolSize(int connectionPoolSize)
|
||||
{
|
||||
this.connectionPoolSize = connectionPoolSize;
|
||||
}
|
||||
|
||||
public int getConnectionPoolSize()
|
||||
{
|
||||
return connectionPoolSize;
|
||||
}
|
||||
|
||||
public String getIssuerUrl()
|
||||
{
|
||||
return issuerUrl;
|
||||
}
|
||||
|
||||
public void setIssuerUrl(String issuerUrl)
|
||||
{
|
||||
this.issuerUrl = issuerUrl;
|
||||
}
|
||||
|
||||
public String getAudience()
|
||||
{
|
||||
return audience;
|
||||
}
|
||||
|
||||
public void setAudience(String audience)
|
||||
{
|
||||
this.audience = audience;
|
||||
}
|
||||
|
||||
public String getAuthServerUrl()
|
||||
{
|
||||
return Optional.ofNullable(realm)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.filter(realm -> StringUtils.isNotBlank(authServerUrl))
|
||||
.map(realm -> UriComponentsBuilder.fromUriString(authServerUrl)
|
||||
.pathSegment(REALMS, realm)
|
||||
.build()
|
||||
.toString())
|
||||
.orElse(authServerUrl);
|
||||
}
|
||||
|
||||
public void setAuthServerUrl(String authServerUrl)
|
||||
{
|
||||
this.authServerUrl = authServerUrl;
|
||||
}
|
||||
|
||||
public String getRealm()
|
||||
{
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(String realm)
|
||||
{
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public String getResource()
|
||||
{
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource)
|
||||
{
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret)
|
||||
{
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public String getClientSecret()
|
||||
{
|
||||
return Optional.ofNullable(clientSecret)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public void setAllowAnyHostname(boolean allowAnyHostname)
|
||||
{
|
||||
this.allowAnyHostname = allowAnyHostname;
|
||||
}
|
||||
|
||||
public boolean isAllowAnyHostname()
|
||||
{
|
||||
return allowAnyHostname;
|
||||
}
|
||||
|
||||
public void setDisableTrustManager(boolean disableTrustManager)
|
||||
{
|
||||
this.disableTrustManager = disableTrustManager;
|
||||
}
|
||||
|
||||
public boolean isDisableTrustManager()
|
||||
{
|
||||
return disableTrustManager;
|
||||
}
|
||||
|
||||
public void setTruststore(String truststore)
|
||||
{
|
||||
this.truststore = truststore;
|
||||
}
|
||||
|
||||
public String getTruststore()
|
||||
{
|
||||
return truststore;
|
||||
}
|
||||
|
||||
public void setTruststorePassword(String truststorePassword)
|
||||
{
|
||||
this.truststorePassword = truststorePassword;
|
||||
}
|
||||
|
||||
public String getTruststorePassword()
|
||||
{
|
||||
return truststorePassword;
|
||||
}
|
||||
|
||||
public void setClientKeystore(String clientKeystore)
|
||||
{
|
||||
this.clientKeystore = clientKeystore;
|
||||
}
|
||||
|
||||
public String getClientKeystore()
|
||||
{
|
||||
return clientKeystore;
|
||||
}
|
||||
|
||||
public void setClientKeystorePassword(String clientKeystorePassword)
|
||||
{
|
||||
this.clientKeystorePassword = clientKeystorePassword;
|
||||
}
|
||||
|
||||
public String getClientKeystorePassword()
|
||||
{
|
||||
return clientKeystorePassword;
|
||||
}
|
||||
|
||||
public void setClientKeyPassword(String clientKeyPassword)
|
||||
{
|
||||
this.clientKeyPassword = clientKeyPassword;
|
||||
}
|
||||
|
||||
public String getClientKeyPassword()
|
||||
{
|
||||
return clientKeyPassword;
|
||||
}
|
||||
|
||||
public void setRealmKey(String realmKey)
|
||||
{
|
||||
this.realmKey = realmKey;
|
||||
}
|
||||
|
||||
public String getRealmKey()
|
||||
{
|
||||
return realmKey;
|
||||
}
|
||||
|
||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl)
|
||||
{
|
||||
this.publicKeyCacheTtl = publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public int getPublicKeyCacheTtl()
|
||||
{
|
||||
return publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public void setPublicClient(boolean publicClient)
|
||||
{
|
||||
this.publicClient = publicClient;
|
||||
}
|
||||
|
||||
public boolean isPublicClient()
|
||||
{
|
||||
return publicClient;
|
||||
}
|
||||
|
||||
public String getPrincipalAttribute()
|
||||
{
|
||||
return principalAttribute;
|
||||
}
|
||||
|
||||
public void setPrincipalAttribute(String principalAttribute)
|
||||
{
|
||||
this.principalAttribute = principalAttribute;
|
||||
}
|
||||
|
||||
public boolean isClientIdValidationDisabled()
|
||||
{
|
||||
return clientIdValidationDisabled;
|
||||
}
|
||||
|
||||
public void setClientIdValidationDisabled(boolean clientIdValidationDisabled)
|
||||
{
|
||||
this.clientIdValidationDisabled = clientIdValidationDisabled;
|
||||
}
|
||||
|
||||
public String getAdminConsoleRedirectPath()
|
||||
{
|
||||
return adminConsoleRedirectPath;
|
||||
}
|
||||
|
||||
public void setAdminConsoleRedirectPath(String adminConsoleRedirectPath)
|
||||
{
|
||||
this.adminConsoleRedirectPath = adminConsoleRedirectPath;
|
||||
}
|
||||
|
||||
public Set<SignatureAlgorithm> getSignatureAlgorithms()
|
||||
{
|
||||
return Stream.of(signatureAlgorithms.split(","))
|
||||
.map(String::trim)
|
||||
.map(SignatureAlgorithm::from)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setSignatureAlgorithms(String signatureAlgorithms)
|
||||
{
|
||||
this.signatureAlgorithms = signatureAlgorithms;
|
||||
}
|
||||
|
||||
public String getIssuerAttribute()
|
||||
{
|
||||
return issuerAttribute;
|
||||
}
|
||||
|
||||
public void setIssuerAttribute(String issuerAttribute)
|
||||
{
|
||||
this.issuerAttribute = issuerAttribute;
|
||||
}
|
||||
|
||||
public Set<String> getAdminConsoleScopes()
|
||||
{
|
||||
return Stream.of(adminConsoleScopes.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setAdminConsoleScopes(String adminConsoleScopes)
|
||||
{
|
||||
this.adminConsoleScopes = adminConsoleScopes;
|
||||
}
|
||||
|
||||
public Set<String> getWebScriptsHomeScopes()
|
||||
{
|
||||
return Stream.of(webScriptsHomeScopes.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setWebScriptsHomeScopes(String webScriptsHomeScopes)
|
||||
{
|
||||
this.webScriptsHomeScopes = webScriptsHomeScopes;
|
||||
}
|
||||
|
||||
public Set<String> getPasswordGrantScopes()
|
||||
{
|
||||
return Stream.of(passwordGrantScopes.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setPasswordGrantScopes(String passwordGrantScopes)
|
||||
{
|
||||
this.passwordGrantScopes = passwordGrantScopes;
|
||||
}
|
||||
|
||||
public void setFirstNameAttribute(String firstNameAttribute)
|
||||
{
|
||||
this.firstNameAttribute = firstNameAttribute;
|
||||
}
|
||||
|
||||
public void setLastNameAttribute(String lastNameAttribute)
|
||||
{
|
||||
this.lastNameAttribute = lastNameAttribute;
|
||||
}
|
||||
|
||||
public void setEmailAttribute(String emailAttribute)
|
||||
{
|
||||
this.emailAttribute = emailAttribute;
|
||||
}
|
||||
|
||||
public void setJwtClockSkewMs(long jwtClockSkewMs)
|
||||
{
|
||||
this.jwtClockSkewMs = jwtClockSkewMs;
|
||||
}
|
||||
|
||||
public String getFirstNameAttribute()
|
||||
{
|
||||
return firstNameAttribute;
|
||||
}
|
||||
|
||||
public String getLastNameAttribute()
|
||||
{
|
||||
return lastNameAttribute;
|
||||
}
|
||||
|
||||
public String getEmailAttribute()
|
||||
{
|
||||
return emailAttribute;
|
||||
}
|
||||
|
||||
public long getJwtClockSkewMs()
|
||||
{
|
||||
return jwtClockSkewMs;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Class to hold configuration for the Identity Service.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
@SuppressWarnings("PMD.ExcessivePublicCount")
|
||||
public class IdentityServiceConfig
|
||||
{
|
||||
private static final String REALMS = "realms";
|
||||
|
||||
private int clientConnectionTimeout;
|
||||
private int clientSocketTimeout;
|
||||
private String issuerUrl;
|
||||
private String audience;
|
||||
// client id
|
||||
private String resource;
|
||||
private String clientSecret;
|
||||
private String authServerUrl;
|
||||
private String realm;
|
||||
private int connectionPoolSize;
|
||||
private boolean allowAnyHostname;
|
||||
private boolean disableTrustManager;
|
||||
private String truststore;
|
||||
private String truststorePassword;
|
||||
private String clientKeystore;
|
||||
private String clientKeystorePassword;
|
||||
private String clientKeyPassword;
|
||||
private String realmKey;
|
||||
private int publicKeyCacheTtl;
|
||||
private boolean publicClient;
|
||||
private String principalAttribute;
|
||||
private boolean clientIdValidationDisabled;
|
||||
private String adminConsoleRedirectPath;
|
||||
private String signatureAlgorithms;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Client connection timeout in milliseconds.
|
||||
*/
|
||||
public int getClientConnectionTimeout()
|
||||
{
|
||||
return clientConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientConnectionTimeout Client connection timeout in milliseconds.
|
||||
*/
|
||||
public void setClientConnectionTimeout(int clientConnectionTimeout)
|
||||
{
|
||||
this.clientConnectionTimeout = clientConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Client socket timeout in milliseconds.s
|
||||
*/
|
||||
public int getClientSocketTimeout()
|
||||
{
|
||||
return clientSocketTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientSocketTimeout Client socket timeout in milliseconds.
|
||||
*/
|
||||
public void setClientSocketTimeout(int clientSocketTimeout)
|
||||
{
|
||||
this.clientSocketTimeout = clientSocketTimeout;
|
||||
}
|
||||
|
||||
public void setConnectionPoolSize(int connectionPoolSize)
|
||||
{
|
||||
this.connectionPoolSize = connectionPoolSize;
|
||||
}
|
||||
|
||||
public int getConnectionPoolSize()
|
||||
{
|
||||
return connectionPoolSize;
|
||||
}
|
||||
|
||||
public String getIssuerUrl()
|
||||
{
|
||||
return issuerUrl;
|
||||
}
|
||||
|
||||
public void setIssuerUrl(String issuerUrl)
|
||||
{
|
||||
this.issuerUrl = issuerUrl;
|
||||
}
|
||||
|
||||
public String getAudience()
|
||||
{
|
||||
return audience;
|
||||
}
|
||||
|
||||
public void setAudience(String audience)
|
||||
{
|
||||
this.audience = audience;
|
||||
}
|
||||
|
||||
public String getAuthServerUrl()
|
||||
{
|
||||
return Optional.ofNullable(realm)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.filter(realm -> StringUtils.isNotBlank(authServerUrl))
|
||||
.map(realm -> UriComponentsBuilder.fromUriString(authServerUrl)
|
||||
.pathSegment(REALMS, realm)
|
||||
.build()
|
||||
.toString())
|
||||
.orElse(authServerUrl);
|
||||
}
|
||||
|
||||
public void setAuthServerUrl(String authServerUrl)
|
||||
{
|
||||
this.authServerUrl = authServerUrl;
|
||||
}
|
||||
|
||||
public String getRealm()
|
||||
{
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(String realm)
|
||||
{
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public String getResource()
|
||||
{
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource)
|
||||
{
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret)
|
||||
{
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public String getClientSecret()
|
||||
{
|
||||
return Optional.ofNullable(clientSecret)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public void setAllowAnyHostname(boolean allowAnyHostname)
|
||||
{
|
||||
this.allowAnyHostname = allowAnyHostname;
|
||||
}
|
||||
|
||||
public boolean isAllowAnyHostname()
|
||||
{
|
||||
return allowAnyHostname;
|
||||
}
|
||||
|
||||
public void setDisableTrustManager(boolean disableTrustManager)
|
||||
{
|
||||
this.disableTrustManager = disableTrustManager;
|
||||
}
|
||||
|
||||
public boolean isDisableTrustManager()
|
||||
{
|
||||
return disableTrustManager;
|
||||
}
|
||||
|
||||
public void setTruststore(String truststore)
|
||||
{
|
||||
this.truststore = truststore;
|
||||
}
|
||||
|
||||
public String getTruststore()
|
||||
{
|
||||
return truststore;
|
||||
}
|
||||
|
||||
public void setTruststorePassword(String truststorePassword)
|
||||
{
|
||||
this.truststorePassword = truststorePassword;
|
||||
}
|
||||
|
||||
public String getTruststorePassword()
|
||||
{
|
||||
return truststorePassword;
|
||||
}
|
||||
|
||||
public void setClientKeystore(String clientKeystore)
|
||||
{
|
||||
this.clientKeystore = clientKeystore;
|
||||
}
|
||||
|
||||
public String getClientKeystore()
|
||||
{
|
||||
return clientKeystore;
|
||||
}
|
||||
|
||||
public void setClientKeystorePassword(String clientKeystorePassword)
|
||||
{
|
||||
this.clientKeystorePassword = clientKeystorePassword;
|
||||
}
|
||||
|
||||
public String getClientKeystorePassword()
|
||||
{
|
||||
return clientKeystorePassword;
|
||||
}
|
||||
|
||||
public void setClientKeyPassword(String clientKeyPassword)
|
||||
{
|
||||
this.clientKeyPassword = clientKeyPassword;
|
||||
}
|
||||
|
||||
public String getClientKeyPassword()
|
||||
{
|
||||
return clientKeyPassword;
|
||||
}
|
||||
|
||||
public void setRealmKey(String realmKey)
|
||||
{
|
||||
this.realmKey = realmKey;
|
||||
}
|
||||
|
||||
public String getRealmKey()
|
||||
{
|
||||
return realmKey;
|
||||
}
|
||||
|
||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl)
|
||||
{
|
||||
this.publicKeyCacheTtl = publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public int getPublicKeyCacheTtl()
|
||||
{
|
||||
return publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public void setPublicClient(boolean publicClient)
|
||||
{
|
||||
this.publicClient = publicClient;
|
||||
}
|
||||
|
||||
public boolean isPublicClient()
|
||||
{
|
||||
return publicClient;
|
||||
}
|
||||
|
||||
public String getPrincipalAttribute()
|
||||
{
|
||||
return principalAttribute;
|
||||
}
|
||||
|
||||
public void setPrincipalAttribute(String principalAttribute)
|
||||
{
|
||||
this.principalAttribute = principalAttribute;
|
||||
}
|
||||
|
||||
public boolean isClientIdValidationDisabled()
|
||||
{
|
||||
return clientIdValidationDisabled;
|
||||
}
|
||||
|
||||
public void setClientIdValidationDisabled(boolean clientIdValidationDisabled)
|
||||
{
|
||||
this.clientIdValidationDisabled = clientIdValidationDisabled;
|
||||
}
|
||||
|
||||
public String getAdminConsoleRedirectPath()
|
||||
{
|
||||
return adminConsoleRedirectPath;
|
||||
}
|
||||
|
||||
public void setAdminConsoleRedirectPath(String adminConsoleRedirectPath)
|
||||
{
|
||||
this.adminConsoleRedirectPath = adminConsoleRedirectPath;
|
||||
}
|
||||
|
||||
public Set<SignatureAlgorithm> getSignatureAlgorithms()
|
||||
{
|
||||
return Stream.of(signatureAlgorithms.split(","))
|
||||
.map(String::trim)
|
||||
.map(SignatureAlgorithm::from)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setSignatureAlgorithms(String signatureAlgorithms)
|
||||
{
|
||||
this.signatureAlgorithms = signatureAlgorithms;
|
||||
}
|
||||
}
|
||||
|
@@ -1,265 +1,249 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static java.util.Objects.nonNull;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.DecodedTokenUser;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
|
||||
|
||||
/**
|
||||
* Allows to interact with the Identity Service
|
||||
*/
|
||||
public interface IdentityServiceFacade
|
||||
{
|
||||
/**
|
||||
* Returns {@link AccessToken} based authorization for provided {@link AuthorizationGrant}.
|
||||
*
|
||||
* @param grant
|
||||
* the OAuth2 grant provided by the Resource Owner.
|
||||
* @return {@link AccessTokenAuthorization} containing access token and optional refresh token.
|
||||
* @throws {@link
|
||||
* AuthorizationException} when provided grant cannot be exchanged for the access token.
|
||||
*/
|
||||
AccessTokenAuthorization authorize(AuthorizationGrant grant) throws AuthorizationException;
|
||||
|
||||
/**
|
||||
* Decodes the access token into the {@link DecodedAccessToken} which contains claims connected with a given token.
|
||||
*
|
||||
* @param token
|
||||
* {@link String} with encoded access token value.
|
||||
* @return {@link DecodedAccessToken} containing decoded claims.
|
||||
* @throws {@link
|
||||
* TokenDecodingException} when token decoding failed.
|
||||
*/
|
||||
DecodedAccessToken decodeToken(String token) throws TokenDecodingException;
|
||||
|
||||
/**
|
||||
* Gets claims about the authenticated user, such as name and email address, via the UserInfo endpoint of the OpenID provider.
|
||||
*
|
||||
* @param token
|
||||
* {@link String} with encoded access token value.
|
||||
* @param userInfoAttrMapping
|
||||
* {@link UserInfoAttrMapping} containing the mapping of claims.
|
||||
* @return {@link DecodedTokenUser} containing user claims or {@link Optional#empty()} if the token does not contain a username claim.
|
||||
*/
|
||||
Optional<DecodedTokenUser> getUserInfo(String token, UserInfoAttrMapping userInfoAttrMapping);
|
||||
|
||||
/**
|
||||
* Gets a client registration
|
||||
*/
|
||||
ClientRegistration getClientRegistration();
|
||||
|
||||
class IdentityServiceFacadeException extends RuntimeException
|
||||
{
|
||||
public IdentityServiceFacadeException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
IdentityServiceFacadeException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthorizationException extends IdentityServiceFacadeException
|
||||
{
|
||||
AuthorizationException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
AuthorizationException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class UserInfoException extends IdentityServiceFacadeException
|
||||
{
|
||||
|
||||
UserInfoException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
UserInfoException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class TokenDecodingException extends IdentityServiceFacadeException
|
||||
{
|
||||
TokenDecodingException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
TokenDecodingException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents access token authorization with optional refresh token.
|
||||
*/
|
||||
interface AccessTokenAuthorization
|
||||
{
|
||||
/**
|
||||
* Required {@link AccessToken}
|
||||
*
|
||||
* @return {@link AccessToken}
|
||||
*/
|
||||
AccessToken getAccessToken();
|
||||
|
||||
/**
|
||||
* Optional refresh token.
|
||||
*
|
||||
* @return Refresh token or {@code null}
|
||||
*/
|
||||
String getRefreshTokenValue();
|
||||
}
|
||||
|
||||
interface AccessToken
|
||||
{
|
||||
String getTokenValue();
|
||||
|
||||
Instant getExpiresAt();
|
||||
}
|
||||
|
||||
interface DecodedAccessToken extends AccessToken
|
||||
{
|
||||
Object getClaim(String claim);
|
||||
}
|
||||
|
||||
class AuthorizationGrant
|
||||
{
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String refreshToken;
|
||||
private final String authorizationCode;
|
||||
private final String redirectUri;
|
||||
|
||||
private AuthorizationGrant(String username, String password, String refreshToken, String authorizationCode, String redirectUri)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.refreshToken = refreshToken;
|
||||
this.authorizationCode = authorizationCode;
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public static AuthorizationGrant password(String username, String password)
|
||||
{
|
||||
return new AuthorizationGrant(requireNonNull(username), requireNonNull(password), null, null, null);
|
||||
}
|
||||
|
||||
public static AuthorizationGrant refreshToken(String refreshToken)
|
||||
{
|
||||
return new AuthorizationGrant(null, null, requireNonNull(refreshToken), null, null);
|
||||
}
|
||||
|
||||
public static AuthorizationGrant authorizationCode(String authorizationCode, String redirectUri)
|
||||
{
|
||||
return new AuthorizationGrant(null, null, null, requireNonNull(authorizationCode), requireNonNull(redirectUri));
|
||||
}
|
||||
|
||||
boolean isPassword()
|
||||
{
|
||||
return nonNull(username);
|
||||
}
|
||||
|
||||
boolean isRefreshToken()
|
||||
{
|
||||
return nonNull(refreshToken);
|
||||
}
|
||||
|
||||
boolean isAuthorizationCode()
|
||||
{
|
||||
return nonNull(authorizationCode);
|
||||
}
|
||||
|
||||
String getUsername()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
String getPassword()
|
||||
{
|
||||
return password;
|
||||
}
|
||||
|
||||
String getRefreshToken()
|
||||
{
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
String getAuthorizationCode()
|
||||
{
|
||||
return authorizationCode;
|
||||
}
|
||||
|
||||
String getRedirectUri()
|
||||
{
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
AuthorizationGrant that = (AuthorizationGrant) o;
|
||||
return Objects.equals(username, that.username) &&
|
||||
Objects.equals(password, that.password) &&
|
||||
Objects.equals(refreshToken, that.refreshToken) &&
|
||||
Objects.equals(authorizationCode, that.authorizationCode) &&
|
||||
Objects.equals(redirectUri, that.redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(username, password, refreshToken, authorizationCode, redirectUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static java.util.Objects.nonNull;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
/**
|
||||
* Allows to interact with the Identity Service
|
||||
*/
|
||||
public interface IdentityServiceFacade
|
||||
{
|
||||
/**
|
||||
* Returns {@link AccessToken} based authorization for provided {@link AuthorizationGrant}.
|
||||
* @param grant the OAuth2 grant provided by the Resource Owner.
|
||||
* @return {@link AccessTokenAuthorization} containing access token and optional refresh token.
|
||||
* @throws {@link AuthorizationException} when provided grant cannot be exchanged for the access token.
|
||||
*/
|
||||
AccessTokenAuthorization authorize(AuthorizationGrant grant) throws AuthorizationException;
|
||||
|
||||
/**
|
||||
* Decodes the access token into the {@link DecodedAccessToken} which contains claims connected with a given token.
|
||||
* @param token {@link String} with encoded access token value.
|
||||
* @return {@link DecodedAccessToken} containing decoded claims.
|
||||
* @throws {@link TokenDecodingException} when token decoding failed.
|
||||
*/
|
||||
DecodedAccessToken decodeToken(String token) throws TokenDecodingException;
|
||||
|
||||
/**
|
||||
* Gets claims about the authenticated user,
|
||||
* such as name and email address, via the UserInfo endpoint of the OpenID provider.
|
||||
* @param token {@link String} with encoded access token value.
|
||||
* @param principalAttribute {@link String} the attribute name used to access the user's name from the user info response.
|
||||
* @return {@link OIDCUserInfo} containing user claims.
|
||||
*/
|
||||
Optional<OIDCUserInfo> getUserInfo(String token, String principalAttribute);
|
||||
|
||||
/**
|
||||
* Gets a client registration
|
||||
*/
|
||||
ClientRegistration getClientRegistration();
|
||||
|
||||
class IdentityServiceFacadeException extends RuntimeException
|
||||
{
|
||||
public IdentityServiceFacadeException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
IdentityServiceFacadeException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthorizationException extends IdentityServiceFacadeException
|
||||
{
|
||||
AuthorizationException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
AuthorizationException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class UserInfoException extends IdentityServiceFacadeException
|
||||
{
|
||||
|
||||
UserInfoException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
UserInfoException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class TokenDecodingException extends IdentityServiceFacadeException
|
||||
{
|
||||
TokenDecodingException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
TokenDecodingException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents access token authorization with optional refresh token.
|
||||
*/
|
||||
interface AccessTokenAuthorization
|
||||
{
|
||||
/**
|
||||
* Required {@link AccessToken}
|
||||
* @return {@link AccessToken}
|
||||
*/
|
||||
AccessToken getAccessToken();
|
||||
|
||||
/**
|
||||
* Optional refresh token.
|
||||
* @return Refresh token or {@code null}
|
||||
*/
|
||||
String getRefreshTokenValue();
|
||||
}
|
||||
|
||||
interface AccessToken {
|
||||
String getTokenValue();
|
||||
Instant getExpiresAt();
|
||||
}
|
||||
|
||||
interface DecodedAccessToken extends AccessToken
|
||||
{
|
||||
Object getClaim(String claim);
|
||||
}
|
||||
|
||||
class AuthorizationGrant {
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String refreshToken;
|
||||
private final String authorizationCode;
|
||||
private final String redirectUri;
|
||||
|
||||
private AuthorizationGrant(String username, String password, String refreshToken, String authorizationCode, String redirectUri)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.refreshToken = refreshToken;
|
||||
this.authorizationCode = authorizationCode;
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public static AuthorizationGrant password(String username, String password)
|
||||
{
|
||||
return new AuthorizationGrant(requireNonNull(username), requireNonNull(password), null, null, null);
|
||||
}
|
||||
|
||||
public static AuthorizationGrant refreshToken(String refreshToken)
|
||||
{
|
||||
return new AuthorizationGrant(null, null, requireNonNull(refreshToken), null, null);
|
||||
}
|
||||
|
||||
public static AuthorizationGrant authorizationCode(String authorizationCode, String redirectUri)
|
||||
{
|
||||
return new AuthorizationGrant(null, null, null, requireNonNull(authorizationCode), requireNonNull(redirectUri));
|
||||
}
|
||||
|
||||
boolean isPassword()
|
||||
{
|
||||
return nonNull(username);
|
||||
}
|
||||
|
||||
boolean isRefreshToken()
|
||||
{
|
||||
return nonNull(refreshToken);
|
||||
}
|
||||
|
||||
boolean isAuthorizationCode()
|
||||
{
|
||||
return nonNull(authorizationCode);
|
||||
}
|
||||
|
||||
String getUsername()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
String getPassword()
|
||||
{
|
||||
return password;
|
||||
}
|
||||
|
||||
String getRefreshToken()
|
||||
{
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
String getAuthorizationCode()
|
||||
{
|
||||
return authorizationCode;
|
||||
}
|
||||
|
||||
String getRedirectUri()
|
||||
{
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
AuthorizationGrant that = (AuthorizationGrant) o;
|
||||
return Objects.equals(username, that.username) &&
|
||||
Objects.equals(password, that.password) &&
|
||||
Objects.equals(refreshToken, that.refreshToken) &&
|
||||
Objects.equals(authorizationCode, that.authorizationCode) &&
|
||||
Objects.equals(redirectUri, that.redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(username, password, refreshToken, authorizationCode, redirectUri);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -30,38 +30,59 @@ import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
|
||||
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.AccessTokenToDecodedTokenUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.DecodedTokenUser;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.OIDCUserInfo;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.TokenUserToOIDCUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.DecodedAccessToken;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* This class handles Just in Time user provisioning. It extracts {@link OIDCUserInfo} from the given bearer token and creates a new user if it does not exist in the repository.
|
||||
* This class handles Just in Time user provisioning. It extracts {@link OIDCUserInfo}
|
||||
* from {@link IdentityServiceFacade.DecodedAccessToken} or {@link UserInfo}
|
||||
* and creates a new user if it does not exist in the repository.
|
||||
*/
|
||||
public class IdentityServiceJITProvisioningHandler
|
||||
{
|
||||
private final IdentityServiceConfig identityServiceConfig;
|
||||
private final IdentityServiceFacade identityServiceFacade;
|
||||
private final PersonService personService;
|
||||
private final TransactionService transactionService;
|
||||
private final IdentityServiceConfig identityServiceConfig;
|
||||
private UserInfoAttrMapping userInfoAttrMapping;
|
||||
private TokenUserToOIDCUserMapper tokenUserToOIDCUserMapper;
|
||||
private AccessTokenToDecodedTokenUserMapper tokenToDecodedTokenUserMapper;
|
||||
|
||||
private final BiFunction<DecodedAccessToken, String, Optional<? extends OIDCUserInfo>> mapTokenToUserInfoResponse = (token, usernameMappingClaim) -> {
|
||||
Optional<String> firstName = Optional.ofNullable(token)
|
||||
.map(jwtToken -> jwtToken.getClaim(PersonClaims.GIVEN_NAME_CLAIM_NAME))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast);
|
||||
Optional<String> lastName = Optional.ofNullable(token)
|
||||
.map(jwtToken -> jwtToken.getClaim(PersonClaims.FAMILY_NAME_CLAIM_NAME))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast);
|
||||
Optional<String> email = Optional.ofNullable(token)
|
||||
.map(jwtToken -> jwtToken.getClaim(PersonClaims.EMAIL_CLAIM_NAME))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast);
|
||||
|
||||
return Optional.ofNullable(token.getClaim(Optional.ofNullable(usernameMappingClaim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElse(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.map(this::normalizeUserId)
|
||||
.map(username -> new OIDCUserInfo(username, firstName.orElse(""), lastName.orElse(""), email.orElse("")));
|
||||
};
|
||||
|
||||
public IdentityServiceJITProvisioningHandler(IdentityServiceFacade identityServiceFacade,
|
||||
PersonService personService,
|
||||
TransactionService transactionService,
|
||||
IdentityServiceConfig identityServiceConfig)
|
||||
PersonService personService,
|
||||
TransactionService transactionService,
|
||||
IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
this.identityServiceFacade = identityServiceFacade;
|
||||
this.personService = personService;
|
||||
@@ -69,95 +90,94 @@ public class IdentityServiceJITProvisioningHandler
|
||||
this.identityServiceConfig = identityServiceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts {@link OIDCUserInfo} from the given bearer token and creates a new user if it does not exist in the repository. Call to the UserInfo endpoint is made only if the token does not contain a username claim or if user needs to be created and some of the {@link OIDCUserInfo} fields are empty.
|
||||
*/
|
||||
public Optional<OIDCUserInfo> extractUserInfoAndCreateUserIfNeeded(String bearerToken)
|
||||
{
|
||||
if (userInfoAttrMapping == null)
|
||||
{
|
||||
initMappers(identityServiceConfig);
|
||||
}
|
||||
Optional<OIDCUserInfo> userInfoResponse = Optional.ofNullable(bearerToken)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.flatMap(token -> extractUserInfoResponseFromAccessToken(token)
|
||||
.filter(userInfo -> StringUtils.isNotEmpty(userInfo.username()))
|
||||
.or(() -> extractUserInfoResponseFromEndpoint(token)));
|
||||
|
||||
Optional<OIDCUserInfo> oidcUserInfo = Optional.ofNullable(bearerToken)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.flatMap(token -> extractUserInfoResponseFromAccessToken(token).filter(decodedTokenUser -> StringUtils.isNotEmpty(decodedTokenUser.username()))
|
||||
.or(() -> extractUserInfoResponseFromEndpoint(token, userInfoAttrMapping)))
|
||||
.map(tokenUserToOIDCUserMapper::toOIDCUser);
|
||||
|
||||
if (transactionService.isReadOnly() || oidcUserInfo.isEmpty())
|
||||
if (transactionService.isReadOnly() || userInfoResponse.isEmpty())
|
||||
{
|
||||
return oidcUserInfo;
|
||||
return userInfoResponse;
|
||||
}
|
||||
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Optional<OIDCUserInfo>>() {
|
||||
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Optional<OIDCUserInfo>>()
|
||||
{
|
||||
@Override
|
||||
public Optional<OIDCUserInfo> doWork() throws Exception
|
||||
{
|
||||
return oidcUserInfo.map(oidcUser -> {
|
||||
if (userDoesNotExistsAndCanBeCreated(oidcUser))
|
||||
return userInfoResponse.map(userInfo -> {
|
||||
if (userInfo.username() != null && personService.createMissingPeople()
|
||||
&& !personService.personExists(userInfo.username()))
|
||||
{
|
||||
|
||||
if (!oidcUser.allFieldsNotEmpty())
|
||||
if (!userInfo.allFieldsNotEmpty())
|
||||
{
|
||||
oidcUser = extractUserInfoResponseFromEndpoint(bearerToken, userInfoAttrMapping)
|
||||
.map(tokenUserToOIDCUserMapper::toOIDCUser)
|
||||
.orElse(oidcUser);
|
||||
userInfo = extractUserInfoResponseFromEndpoint(bearerToken).orElse(userInfo);
|
||||
}
|
||||
createPerson(oidcUser);
|
||||
Map<QName, Serializable> properties = new HashMap<>();
|
||||
properties.put(ContentModel.PROP_USERNAME, userInfo.username());
|
||||
properties.put(ContentModel.PROP_FIRSTNAME, userInfo.firstName());
|
||||
properties.put(ContentModel.PROP_LASTNAME, userInfo.lastName());
|
||||
properties.put(ContentModel.PROP_EMAIL, userInfo.email());
|
||||
properties.put(ContentModel.PROP_ORGID, "");
|
||||
properties.put(ContentModel.PROP_HOME_FOLDER_PROVIDER, null);
|
||||
|
||||
properties.put(ContentModel.PROP_SIZE_CURRENT, 0L);
|
||||
properties.put(ContentModel.PROP_SIZE_QUOTA, -1L); // no quota
|
||||
|
||||
personService.createPerson(properties);
|
||||
}
|
||||
return oidcUser;
|
||||
return userInfo;
|
||||
});
|
||||
}
|
||||
|
||||
}, AuthenticationUtil.getSystemUserName());
|
||||
}
|
||||
|
||||
private void initMappers(IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
this.userInfoAttrMapping = initUserInfoAttrMapping(identityServiceConfig);
|
||||
this.tokenUserToOIDCUserMapper = new TokenUserToOIDCUserMapper(personService);
|
||||
this.tokenToDecodedTokenUserMapper = new AccessTokenToDecodedTokenUserMapper(userInfoAttrMapping);
|
||||
}
|
||||
|
||||
private boolean userDoesNotExistsAndCanBeCreated(OIDCUserInfo userInfo)
|
||||
{
|
||||
return userInfo.username() != null && personService.createMissingPeople()
|
||||
&& !personService.personExists(userInfo.username());
|
||||
}
|
||||
|
||||
private Optional<DecodedTokenUser> extractUserInfoResponseFromAccessToken(String bearerToken)
|
||||
private Optional<OIDCUserInfo> extractUserInfoResponseFromAccessToken(String bearerToken)
|
||||
{
|
||||
return Optional.ofNullable(bearerToken)
|
||||
.map(identityServiceFacade::decodeToken)
|
||||
.flatMap(tokenToDecodedTokenUserMapper::toDecodedTokenUser);
|
||||
.map(identityServiceFacade::decodeToken)
|
||||
.flatMap(decodedToken -> mapTokenToUserInfoResponse.apply(decodedToken,
|
||||
identityServiceConfig.getPrincipalAttribute()));
|
||||
}
|
||||
|
||||
private Optional<DecodedTokenUser> extractUserInfoResponseFromEndpoint(String bearerToken, UserInfoAttrMapping userInfoAttrMapping)
|
||||
private Optional<OIDCUserInfo> extractUserInfoResponseFromEndpoint(String bearerToken)
|
||||
{
|
||||
return identityServiceFacade.getUserInfo(bearerToken, userInfoAttrMapping)
|
||||
.filter(userInfo -> userInfo.username() != null && !userInfo.username().isEmpty());
|
||||
return identityServiceFacade.getUserInfo(bearerToken,
|
||||
StringUtils.isNotBlank(identityServiceConfig.getPrincipalAttribute()) ?
|
||||
identityServiceConfig.getPrincipalAttribute() : PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)
|
||||
.filter(userInfo -> userInfo.username() != null && !userInfo.username().isEmpty())
|
||||
.map(userInfo -> new OIDCUserInfo(normalizeUserId(userInfo.username()),
|
||||
Optional.ofNullable(userInfo.firstName()).orElse(""),
|
||||
Optional.ofNullable(userInfo.lastName()).orElse(""),
|
||||
Optional.ofNullable(userInfo.email()).orElse("")));
|
||||
}
|
||||
|
||||
private void createPerson(OIDCUserInfo userInfo)
|
||||
/**
|
||||
* Normalizes a user id, taking into account existing user accounts and case sensitivity settings.
|
||||
*
|
||||
* @param userId the user id
|
||||
* @return the string
|
||||
*/
|
||||
private String normalizeUserId(final String userId)
|
||||
{
|
||||
Map<QName, Serializable> properties = new HashMap<>();
|
||||
properties.put(ContentModel.PROP_USERNAME, userInfo.username());
|
||||
properties.put(ContentModel.PROP_FIRSTNAME, userInfo.firstName());
|
||||
properties.put(ContentModel.PROP_LASTNAME, userInfo.lastName());
|
||||
properties.put(ContentModel.PROP_EMAIL, userInfo.email());
|
||||
properties.put(ContentModel.PROP_ORGID, "");
|
||||
properties.put(ContentModel.PROP_HOME_FOLDER_PROVIDER, null);
|
||||
properties.put(ContentModel.PROP_SIZE_CURRENT, 0L);
|
||||
properties.put(ContentModel.PROP_SIZE_QUOTA, -1L); // no quota
|
||||
if (userId == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
personService.createPerson(properties);
|
||||
String normalized = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<String>()
|
||||
{
|
||||
@Override
|
||||
public String doWork() throws Exception
|
||||
{
|
||||
return personService.getUserIdentifier(userId);
|
||||
}
|
||||
}, AuthenticationUtil.getSystemUserName());
|
||||
|
||||
return normalized == null ? userId : normalized;
|
||||
}
|
||||
|
||||
private UserInfoAttrMapping initUserInfoAttrMapping(IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
return new UserInfoAttrMapping(identityServiceFacade.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(),
|
||||
identityServiceConfig.getFirstNameAttribute(),
|
||||
identityServiceConfig.getLastNameAttribute(),
|
||||
identityServiceConfig.getEmailAttribute());
|
||||
}
|
||||
}
|
||||
|
@@ -1,181 +1,181 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.Optional;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.OIDCUserInfo;
|
||||
|
||||
/**
|
||||
* A {@link RemoteUserMapper} implementation that detects and validates JWTs issued by the Alfresco Identity Service.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, ActivateableBean
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(IdentityServiceRemoteUserMapper.class);
|
||||
|
||||
/** Is the mapper enabled */
|
||||
private boolean isEnabled;
|
||||
|
||||
/** Are token validation failures handled silently? */
|
||||
private boolean isValidationFailureSilent;
|
||||
|
||||
private BearerTokenResolver bearerTokenResolver;
|
||||
|
||||
private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
|
||||
|
||||
/**
|
||||
* Sets the active flag
|
||||
*
|
||||
* @param isEnabled
|
||||
* true to enable the subsystem
|
||||
*/
|
||||
public void setActive(boolean isEnabled)
|
||||
{
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether token validation failures are silent
|
||||
*
|
||||
* @param silent
|
||||
* true to silently fail, false to throw an exception
|
||||
*/
|
||||
public void setValidationFailureSilent(boolean silent)
|
||||
{
|
||||
this.isValidationFailureSilent = silent;
|
||||
}
|
||||
|
||||
public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver)
|
||||
{
|
||||
this.bearerTokenResolver = bearerTokenResolver;
|
||||
}
|
||||
|
||||
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
|
||||
{
|
||||
this.jitProvisioningHandler = jitProvisioningHandler;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
*
|
||||
* @see org.alfresco.web.app.servlet.RemoteUserMapper#getRemoteUser(jakarta.servlet.http.HttpServletRequest) */
|
||||
@Override
|
||||
public String getRemoteUser(HttpServletRequest request)
|
||||
{
|
||||
LOGGER.trace("Retrieving username from http request...");
|
||||
|
||||
if (!this.isEnabled)
|
||||
{
|
||||
LOGGER.debug("IdentityServiceRemoteUserMapper is disabled, returning null.");
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
String normalizedUserId = extractUserFromHeader(request);
|
||||
|
||||
if (normalizedUserId != null)
|
||||
{
|
||||
// Normalize the user ID taking into account case sensitivity settings
|
||||
LOGGER.trace("Returning userId: " + AuthenticationUtil.maskUsername(normalizedUserId));
|
||||
return normalizedUserId;
|
||||
}
|
||||
}
|
||||
catch (IdentityServiceFacadeException e)
|
||||
{
|
||||
if (!isValidationFailureSilent)
|
||||
{
|
||||
throw new AuthenticationException("Failed to extract username from token: " + e.getMessage(), e);
|
||||
}
|
||||
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
|
||||
}
|
||||
LOGGER.trace("Could not identify a userId. Returning null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
*
|
||||
* @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive() */
|
||||
public boolean isActive()
|
||||
{
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the user name from the JWT in the given request.
|
||||
*
|
||||
* @param request
|
||||
* The request containing the JWT
|
||||
* @return The username or null if it can not be determined
|
||||
*/
|
||||
private String extractUserFromHeader(HttpServletRequest request)
|
||||
{
|
||||
// try authenticating with bearer token first
|
||||
LOGGER.debug("Trying bearer token...");
|
||||
|
||||
final String bearerToken;
|
||||
try
|
||||
{
|
||||
bearerToken = bearerTokenResolver.resolve(request);
|
||||
}
|
||||
catch (OAuth2AuthenticationException e)
|
||||
{
|
||||
LOGGER.debug("Failed to resolve Bearer token.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
final Optional<String> possibleUsername = jitProvisioningHandler
|
||||
.extractUserInfoAndCreateUserIfNeeded(bearerToken)
|
||||
.map(OIDCUserInfo::username);
|
||||
|
||||
if (possibleUsername.isEmpty())
|
||||
{
|
||||
LOGGER.debug("User could not be authenticated by IdentityServiceRemoteUserMapper.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedUsername = possibleUsername.get();
|
||||
LOGGER.trace("Extracted username: " + AuthenticationUtil.maskUsername(normalizedUsername));
|
||||
|
||||
return normalizedUsername;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
|
||||
/**
|
||||
* A {@link RemoteUserMapper} implementation that detects and validates JWTs
|
||||
* issued by the Alfresco Identity Service.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, ActivateableBean
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(IdentityServiceRemoteUserMapper.class);
|
||||
|
||||
/** Is the mapper enabled */
|
||||
private boolean isEnabled;
|
||||
|
||||
/** Are token validation failures handled silently? */
|
||||
private boolean isValidationFailureSilent;
|
||||
|
||||
private BearerTokenResolver bearerTokenResolver;
|
||||
|
||||
private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
|
||||
|
||||
/**
|
||||
* Sets the active flag
|
||||
*
|
||||
* @param isEnabled true to enable the subsystem
|
||||
*/
|
||||
public void setActive(boolean isEnabled)
|
||||
{
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether token validation failures are silent
|
||||
*
|
||||
* @param silent true to silently fail, false to throw an exception
|
||||
*/
|
||||
public void setValidationFailureSilent(boolean silent)
|
||||
{
|
||||
this.isValidationFailureSilent = silent;
|
||||
}
|
||||
|
||||
public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver)
|
||||
{
|
||||
this.bearerTokenResolver = bearerTokenResolver;
|
||||
}
|
||||
|
||||
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
|
||||
{
|
||||
this.jitProvisioningHandler = jitProvisioningHandler;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.web.app.servlet.RemoteUserMapper#getRemoteUser(jakarta.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
public String getRemoteUser(HttpServletRequest request)
|
||||
{
|
||||
LOGGER.trace("Retrieving username from http request...");
|
||||
|
||||
if (!this.isEnabled)
|
||||
{
|
||||
LOGGER.debug("IdentityServiceRemoteUserMapper is disabled, returning null.");
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
String normalizedUserId = extractUserFromHeader(request);
|
||||
|
||||
|
||||
if (normalizedUserId != null)
|
||||
{
|
||||
// Normalize the user ID taking into account case sensitivity settings
|
||||
LOGGER.trace("Returning userId: " + AuthenticationUtil.maskUsername(normalizedUserId));
|
||||
return normalizedUserId;
|
||||
}
|
||||
}
|
||||
catch (IdentityServiceFacadeException e)
|
||||
{
|
||||
if (!isValidationFailureSilent)
|
||||
{
|
||||
throw new AuthenticationException("Failed to extract username from token: " + e.getMessage(), e);
|
||||
}
|
||||
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
|
||||
}
|
||||
LOGGER.trace("Could not identify a userId. Returning null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive()
|
||||
*/
|
||||
public boolean isActive()
|
||||
{
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the user name from the JWT in the given request.
|
||||
*
|
||||
* @param request The request containing the JWT
|
||||
* @return The username or null if it can not be determined
|
||||
*/
|
||||
private String extractUserFromHeader(HttpServletRequest request)
|
||||
{
|
||||
// try authenticating with bearer token first
|
||||
LOGGER.debug("Trying bearer token...");
|
||||
|
||||
final String bearerToken;
|
||||
try
|
||||
{
|
||||
bearerToken = bearerTokenResolver.resolve(request);
|
||||
}
|
||||
catch (OAuth2AuthenticationException e)
|
||||
{
|
||||
LOGGER.debug("Failed to resolve Bearer token.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
final Optional<String> possibleUsername = jitProvisioningHandler
|
||||
.extractUserInfoAndCreateUserIfNeeded(bearerToken)
|
||||
.map(OIDCUserInfo::username);
|
||||
|
||||
if (possibleUsername.isEmpty())
|
||||
{
|
||||
LOGGER.debug("User could not be authenticated by IdentityServiceRemoteUserMapper.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedUsername = possibleUsername.get();
|
||||
LOGGER.trace("Extracted username: " + AuthenticationUtil.maskUsername(normalizedUsername));
|
||||
|
||||
return normalizedUsername;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.user;
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
@@ -1,347 +1,344 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceMetadataKey.AUDIENCE;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequestEntityConverter;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.DecodedTokenUser;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
|
||||
|
||||
class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(SpringBasedIdentityServiceFacade.class);
|
||||
private static final Instant SOME_INSIGNIFICANT_DATE_IN_THE_PAST = Instant.MIN.plusSeconds(12345);
|
||||
private final Map<AuthorizationGrantType, OAuth2AccessTokenResponseClient> clients;
|
||||
private final DefaultOAuth2UserService defaultOAuth2UserService;
|
||||
private final ClientRegistration clientRegistration;
|
||||
private final JwtDecoder jwtDecoder;
|
||||
|
||||
SpringBasedIdentityServiceFacade(RestOperations restOperations, ClientRegistration clientRegistration,
|
||||
JwtDecoder jwtDecoder)
|
||||
{
|
||||
requireNonNull(restOperations);
|
||||
this.clientRegistration = requireNonNull(clientRegistration);
|
||||
this.jwtDecoder = requireNonNull(jwtDecoder);
|
||||
this.clients = Map.of(
|
||||
AuthorizationGrantType.AUTHORIZATION_CODE, createAuthorizationCodeClient(restOperations),
|
||||
AuthorizationGrantType.REFRESH_TOKEN, createRefreshTokenClient(restOperations),
|
||||
AuthorizationGrantType.PASSWORD, createPasswordClient(restOperations, clientRegistration));
|
||||
this.defaultOAuth2UserService = createOAuth2UserService(restOperations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessTokenAuthorization authorize(AuthorizationGrant authorizationGrant)
|
||||
{
|
||||
final AbstractOAuth2AuthorizationGrantRequest request = createRequest(authorizationGrant);
|
||||
final OAuth2AccessTokenResponseClient client = getClient(request);
|
||||
|
||||
final OAuth2AccessTokenResponse response;
|
||||
try
|
||||
{
|
||||
response = client.getTokenResponse(request);
|
||||
}
|
||||
catch (OAuth2AuthorizationException e)
|
||||
{
|
||||
LOGGER.debug("Failed to authorize against Authorization Server. Reason: " + e.getError() + ".");
|
||||
throw new AuthorizationException("Failed to obtain access token. " + e.getError(), e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOGGER.warn("Failed to authorize against Authorization Server. Reason: " + e.getMessage());
|
||||
throw new AuthorizationException("Failed to obtain access token.", e);
|
||||
}
|
||||
|
||||
return new SpringAccessTokenAuthorization(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DecodedTokenUser> getUserInfo(String token, UserInfoAttrMapping userInfoAttrMapping)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Optional.ofNullable(defaultOAuth2UserService.loadUser(new OAuth2UserRequest(clientRegistration, getSpringAccessToken(token))))
|
||||
.flatMap(oAuth2User -> mapOAuth2UserToDecodedTokenUser(oAuth2User, userInfoAttrMapping));
|
||||
}
|
||||
catch (OAuth2AuthenticationException exception)
|
||||
{
|
||||
LOGGER.warn("User Info Request failed: " + exception.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistration getClientRegistration()
|
||||
{
|
||||
return clientRegistration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecodedAccessToken decodeToken(String token)
|
||||
{
|
||||
final Jwt validToken;
|
||||
try
|
||||
{
|
||||
validToken = jwtDecoder.decode(token);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new TokenDecodingException("Failed to decode token. " + e.getMessage(), e);
|
||||
}
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Bearer token outcome: " + validToken.getClaims());
|
||||
}
|
||||
return new SpringDecodedAccessToken(validToken);
|
||||
}
|
||||
|
||||
private AbstractOAuth2AuthorizationGrantRequest createRequest(AuthorizationGrant grant)
|
||||
{
|
||||
if (grant.isPassword())
|
||||
{
|
||||
return new OAuth2PasswordGrantRequest(clientRegistration, grant.getUsername(), grant.getPassword());
|
||||
}
|
||||
|
||||
if (grant.isRefreshToken())
|
||||
{
|
||||
final OAuth2AccessToken expiredAccessToken = getSpringAccessToken("JUST_FOR_FULFILLING_THE_SPRING_API");
|
||||
final OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(grant.getRefreshToken(), null);
|
||||
|
||||
return new OAuth2RefreshTokenGrantRequest(clientRegistration, expiredAccessToken, refreshToken,
|
||||
clientRegistration.getScopes());
|
||||
}
|
||||
|
||||
if (grant.isAuthorizationCode())
|
||||
{
|
||||
final OAuth2AuthorizationExchange authzExchange = new OAuth2AuthorizationExchange(
|
||||
OAuth2AuthorizationRequest.authorizationCode()
|
||||
.clientId(clientRegistration.getClientId())
|
||||
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
|
||||
.redirectUri(grant.getRedirectUri())
|
||||
.scopes(clientRegistration.getScopes())
|
||||
.build(),
|
||||
OAuth2AuthorizationResponse.success(grant.getAuthorizationCode())
|
||||
.redirectUri(grant.getRedirectUri())
|
||||
.build());
|
||||
return new OAuth2AuthorizationCodeGrantRequest(clientRegistration, authzExchange);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Unsupported grant type.");
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponseClient getClient(AbstractOAuth2AuthorizationGrantRequest request)
|
||||
{
|
||||
final AuthorizationGrantType grantType = request.getGrantType();
|
||||
final OAuth2AccessTokenResponseClient client = clients.get(grantType);
|
||||
if (client == null)
|
||||
{
|
||||
throw new UnsupportedOperationException("Unsupported grant type `" + grantType + "`.");
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> createAuthorizationCodeClient(
|
||||
RestOperations rest)
|
||||
{
|
||||
final DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
|
||||
client.setRestOperations(rest);
|
||||
return client;
|
||||
}
|
||||
|
||||
private static OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> createRefreshTokenClient(
|
||||
RestOperations rest)
|
||||
{
|
||||
final DefaultRefreshTokenTokenResponseClient client = new DefaultRefreshTokenTokenResponseClient();
|
||||
client.setRestOperations(rest);
|
||||
return client;
|
||||
}
|
||||
|
||||
private static DefaultOAuth2UserService createOAuth2UserService(RestOperations rest)
|
||||
{
|
||||
final DefaultOAuth2UserService userService = new DefaultOAuth2UserService();
|
||||
userService.setRestOperations(rest);
|
||||
return userService;
|
||||
}
|
||||
|
||||
private Optional<DecodedTokenUser> mapOAuth2UserToDecodedTokenUser(OAuth2User oAuth2User, UserInfoAttrMapping userInfoAttrMapping)
|
||||
{
|
||||
Optional<String> preferredUsername = Optional.ofNullable(oAuth2User.getAttribute(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.filter(StringUtils::isNotEmpty);
|
||||
Optional<String> userName = Optional.ofNullable(oAuth2User.getName()).filter(username -> !username.isEmpty()).or(() -> preferredUsername);
|
||||
return userName.map(name -> DecodedTokenUser.validateAndCreate(name,
|
||||
oAuth2User.getAttribute(userInfoAttrMapping.firstNameClaim()),
|
||||
oAuth2User.getAttribute(userInfoAttrMapping.lastNameClaim()),
|
||||
oAuth2User.getAttribute(userInfoAttrMapping.emailClaim())));
|
||||
}
|
||||
|
||||
private static OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> createPasswordClient(RestOperations rest,
|
||||
ClientRegistration clientRegistration)
|
||||
{
|
||||
final DefaultPasswordTokenResponseClient client = new DefaultPasswordTokenResponseClient();
|
||||
client.setRestOperations(rest);
|
||||
Optional.of(clientRegistration)
|
||||
.map(ClientRegistration::getProviderDetails)
|
||||
.map(ProviderDetails::getConfigurationMetadata)
|
||||
.map(metadata -> metadata.get(AUDIENCE.getValue()))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.ifPresent(audienceValue -> {
|
||||
final OAuth2PasswordGrantRequestEntityConverter requestEntityConverter = new OAuth2PasswordGrantRequestEntityConverter();
|
||||
requestEntityConverter.addParametersConverter(audienceParameterConverter(audienceValue));
|
||||
client.setRequestEntityConverter(requestEntityConverter);
|
||||
});
|
||||
return client;
|
||||
}
|
||||
|
||||
private static Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> audienceParameterConverter(
|
||||
String audienceValue)
|
||||
{
|
||||
return (grantRequest) -> {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set("audience", audienceValue);
|
||||
|
||||
return parameters;
|
||||
};
|
||||
}
|
||||
|
||||
private static OAuth2AccessToken getSpringAccessToken(String token)
|
||||
{
|
||||
// Just for fulfilling the Spring API
|
||||
return new OAuth2AccessToken(
|
||||
TokenType.BEARER,
|
||||
token,
|
||||
SOME_INSIGNIFICANT_DATE_IN_THE_PAST,
|
||||
SOME_INSIGNIFICANT_DATE_IN_THE_PAST.plusSeconds(1));
|
||||
}
|
||||
|
||||
private static class SpringAccessTokenAuthorization implements AccessTokenAuthorization
|
||||
{
|
||||
private final OAuth2AccessTokenResponse tokenResponse;
|
||||
|
||||
private SpringAccessTokenAuthorization(OAuth2AccessTokenResponse tokenResponse)
|
||||
{
|
||||
this.tokenResponse = requireNonNull(tokenResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessToken getAccessToken()
|
||||
{
|
||||
return new SpringAccessToken(tokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefreshTokenValue()
|
||||
{
|
||||
return Optional.of(tokenResponse)
|
||||
.map(OAuth2AccessTokenResponse::getRefreshToken)
|
||||
.map(AbstractOAuth2Token::getTokenValue)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpringAccessToken implements AccessToken
|
||||
{
|
||||
private final AbstractOAuth2Token token;
|
||||
|
||||
private SpringAccessToken(AbstractOAuth2Token token)
|
||||
{
|
||||
this.token = requireNonNull(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTokenValue()
|
||||
{
|
||||
return token.getTokenValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getExpiresAt()
|
||||
{
|
||||
return token.getExpiresAt();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpringDecodedAccessToken extends SpringAccessToken implements DecodedAccessToken
|
||||
{
|
||||
private final Jwt jwt;
|
||||
|
||||
private SpringDecodedAccessToken(Jwt jwt)
|
||||
{
|
||||
super(jwt);
|
||||
this.jwt = jwt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getClaim(String claim)
|
||||
{
|
||||
return jwt.getClaim(claim);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceMetadataKey.AUDIENCE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.ParseException;
|
||||
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoRequest;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoResponse;
|
||||
import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequestEntityConverter;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(SpringBasedIdentityServiceFacade.class);
|
||||
private static final Instant SOME_INSIGNIFICANT_DATE_IN_THE_PAST = Instant.MIN.plusSeconds(12345);
|
||||
private final Map<AuthorizationGrantType, OAuth2AccessTokenResponseClient> clients;
|
||||
private final ClientRegistration clientRegistration;
|
||||
private final JwtDecoder jwtDecoder;
|
||||
|
||||
SpringBasedIdentityServiceFacade(RestOperations restOperations, ClientRegistration clientRegistration,
|
||||
JwtDecoder jwtDecoder)
|
||||
{
|
||||
requireNonNull(restOperations);
|
||||
this.clientRegistration = requireNonNull(clientRegistration);
|
||||
this.jwtDecoder = requireNonNull(jwtDecoder);
|
||||
this.clients = Map.of(
|
||||
AuthorizationGrantType.AUTHORIZATION_CODE, createAuthorizationCodeClient(restOperations),
|
||||
AuthorizationGrantType.REFRESH_TOKEN, createRefreshTokenClient(restOperations),
|
||||
AuthorizationGrantType.PASSWORD, createPasswordClient(restOperations, clientRegistration));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessTokenAuthorization authorize(AuthorizationGrant authorizationGrant)
|
||||
{
|
||||
final AbstractOAuth2AuthorizationGrantRequest request = createRequest(authorizationGrant);
|
||||
final OAuth2AccessTokenResponseClient client = getClient(request);
|
||||
|
||||
final OAuth2AccessTokenResponse response;
|
||||
try
|
||||
{
|
||||
response = client.getTokenResponse(request);
|
||||
}
|
||||
catch (OAuth2AuthorizationException e)
|
||||
{
|
||||
LOGGER.debug("Failed to authorize against Authorization Server. Reason: " + e.getError() + ".");
|
||||
throw new AuthorizationException("Failed to obtain access token. " + e.getError(), e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOGGER.warn("Failed to authorize against Authorization Server. Reason: " + e.getMessage());
|
||||
throw new AuthorizationException("Failed to obtain access token.", e);
|
||||
}
|
||||
|
||||
return new SpringAccessTokenAuthorization(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<OIDCUserInfo> getUserInfo(String tokenParameter, String principalAttribute)
|
||||
{
|
||||
return Optional.ofNullable(tokenParameter)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.flatMap(token -> Optional.ofNullable(clientRegistration)
|
||||
.map(ClientRegistration::getProviderDetails)
|
||||
.map(ClientRegistration.ProviderDetails::getUserInfoEndpoint)
|
||||
.map(ClientRegistration.ProviderDetails.UserInfoEndpoint::getUri)
|
||||
.flatMap(uri -> {
|
||||
try
|
||||
{
|
||||
return Optional.of(
|
||||
new UserInfoRequest(new URI(uri), new BearerAccessToken(token)).toHTTPRequest().send());
|
||||
}
|
||||
catch (IOException | URISyntaxException e)
|
||||
{
|
||||
LOGGER.warn("Failed to get user information. Reason: " + e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.flatMap(httpResponse -> {
|
||||
try
|
||||
{
|
||||
return Optional.of(UserInfoResponse.parse(httpResponse));
|
||||
}
|
||||
catch (ParseException e)
|
||||
{
|
||||
LOGGER.warn("Failed to parse user info response. Reason: " + e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.map(UserInfoResponse::toSuccessResponse)
|
||||
.map(UserInfoSuccessResponse::getUserInfo))
|
||||
.map(userInfo -> new OIDCUserInfo(userInfo.getStringClaim(principalAttribute), userInfo.getGivenName(),
|
||||
userInfo.getFamilyName(), userInfo.getEmailAddress()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistration getClientRegistration()
|
||||
{
|
||||
return clientRegistration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecodedAccessToken decodeToken(String token)
|
||||
{
|
||||
final Jwt validToken;
|
||||
try
|
||||
{
|
||||
validToken = jwtDecoder.decode(token);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new TokenDecodingException("Failed to decode token. " + e.getMessage(), e);
|
||||
}
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Bearer token outcome: " + validToken.getClaims());
|
||||
}
|
||||
return new SpringDecodedAccessToken(validToken);
|
||||
}
|
||||
|
||||
private AbstractOAuth2AuthorizationGrantRequest createRequest(AuthorizationGrant grant)
|
||||
{
|
||||
if (grant.isPassword())
|
||||
{
|
||||
return new OAuth2PasswordGrantRequest(clientRegistration, grant.getUsername(), grant.getPassword());
|
||||
}
|
||||
|
||||
if (grant.isRefreshToken())
|
||||
{
|
||||
final OAuth2AccessToken expiredAccessToken = new OAuth2AccessToken(
|
||||
TokenType.BEARER,
|
||||
"JUST_FOR_FULFILLING_THE_SPRING_API",
|
||||
SOME_INSIGNIFICANT_DATE_IN_THE_PAST,
|
||||
SOME_INSIGNIFICANT_DATE_IN_THE_PAST.plusSeconds(1));
|
||||
final OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(grant.getRefreshToken(), null);
|
||||
|
||||
return new OAuth2RefreshTokenGrantRequest(clientRegistration, expiredAccessToken, refreshToken,
|
||||
clientRegistration.getScopes());
|
||||
}
|
||||
|
||||
if (grant.isAuthorizationCode())
|
||||
{
|
||||
final OAuth2AuthorizationExchange authzExchange = new OAuth2AuthorizationExchange(
|
||||
OAuth2AuthorizationRequest.authorizationCode()
|
||||
.clientId(clientRegistration.getClientId())
|
||||
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
|
||||
.redirectUri(grant.getRedirectUri())
|
||||
.scopes(clientRegistration.getScopes())
|
||||
.build(),
|
||||
OAuth2AuthorizationResponse.success(grant.getAuthorizationCode())
|
||||
.redirectUri(grant.getRedirectUri())
|
||||
.build()
|
||||
);
|
||||
return new OAuth2AuthorizationCodeGrantRequest(clientRegistration, authzExchange);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Unsupported grant type.");
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponseClient getClient(AbstractOAuth2AuthorizationGrantRequest request)
|
||||
{
|
||||
final AuthorizationGrantType grantType = request.getGrantType();
|
||||
final OAuth2AccessTokenResponseClient client = clients.get(grantType);
|
||||
if (client == null)
|
||||
{
|
||||
throw new UnsupportedOperationException("Unsupported grant type `" + grantType + "`.");
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> createAuthorizationCodeClient(
|
||||
RestOperations rest)
|
||||
{
|
||||
final DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
|
||||
client.setRestOperations(rest);
|
||||
return client;
|
||||
}
|
||||
|
||||
private static OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> createRefreshTokenClient(
|
||||
RestOperations rest)
|
||||
{
|
||||
final DefaultRefreshTokenTokenResponseClient client = new DefaultRefreshTokenTokenResponseClient();
|
||||
client.setRestOperations(rest);
|
||||
return client;
|
||||
}
|
||||
|
||||
private static OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> createPasswordClient(RestOperations rest,
|
||||
ClientRegistration clientRegistration)
|
||||
{
|
||||
final DefaultPasswordTokenResponseClient client = new DefaultPasswordTokenResponseClient();
|
||||
client.setRestOperations(rest);
|
||||
Optional.of(clientRegistration)
|
||||
.map(ClientRegistration::getProviderDetails)
|
||||
.map(ProviderDetails::getConfigurationMetadata)
|
||||
.map(metadata -> metadata.get(AUDIENCE.getValue()))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.ifPresent(audienceValue -> {
|
||||
final OAuth2PasswordGrantRequestEntityConverter requestEntityConverter = new OAuth2PasswordGrantRequestEntityConverter();
|
||||
requestEntityConverter.addParametersConverter(audienceParameterConverter(audienceValue));
|
||||
client.setRequestEntityConverter(requestEntityConverter);
|
||||
});
|
||||
return client;
|
||||
}
|
||||
|
||||
private static Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> audienceParameterConverter(
|
||||
String audienceValue)
|
||||
{
|
||||
return (grantRequest) -> {
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set("audience", audienceValue);
|
||||
|
||||
return parameters;
|
||||
};
|
||||
}
|
||||
|
||||
private static class SpringAccessTokenAuthorization implements AccessTokenAuthorization
|
||||
{
|
||||
private final OAuth2AccessTokenResponse tokenResponse;
|
||||
|
||||
private SpringAccessTokenAuthorization(OAuth2AccessTokenResponse tokenResponse)
|
||||
{
|
||||
this.tokenResponse = requireNonNull(tokenResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessToken getAccessToken()
|
||||
{
|
||||
return new SpringAccessToken(tokenResponse.getAccessToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefreshTokenValue()
|
||||
{
|
||||
return Optional.of(tokenResponse)
|
||||
.map(OAuth2AccessTokenResponse::getRefreshToken)
|
||||
.map(AbstractOAuth2Token::getTokenValue)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpringAccessToken implements AccessToken
|
||||
{
|
||||
private final AbstractOAuth2Token token;
|
||||
|
||||
private SpringAccessToken(AbstractOAuth2Token token)
|
||||
{
|
||||
this.token = requireNonNull(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTokenValue()
|
||||
{
|
||||
return token.getTokenValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getExpiresAt()
|
||||
{
|
||||
return token.getExpiresAt();
|
||||
}
|
||||
}
|
||||
|
||||
private static class SpringDecodedAccessToken extends SpringAccessToken implements DecodedAccessToken
|
||||
{
|
||||
private final Jwt jwt;
|
||||
|
||||
private SpringDecodedAccessToken(Jwt jwt)
|
||||
{
|
||||
super(jwt);
|
||||
this.jwt = jwt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getClaim(String claim)
|
||||
{
|
||||
return jwt.getClaim(claim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.authentication;
|
||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@@ -33,12 +33,12 @@ import org.alfresco.repo.admin.SysAdminParams;
|
||||
/**
|
||||
* Service to handle Admin Console authentication-related cookies.
|
||||
*/
|
||||
public class AdminAuthenticationCookiesService
|
||||
public class AdminConsoleAuthenticationCookiesService
|
||||
{
|
||||
private final SysAdminParams sysAdminParams;
|
||||
private final int cookieLifetime;
|
||||
|
||||
public AdminAuthenticationCookiesService(SysAdminParams sysAdminParams, int cookieLifetime)
|
||||
public AdminConsoleAuthenticationCookiesService(SysAdminParams sysAdminParams, int cookieLifetime)
|
||||
{
|
||||
this.sysAdminParams = sysAdminParams;
|
||||
this.cookieLifetime = cookieLifetime;
|
@@ -23,7 +23,7 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.authentication;
|
||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.enumeration;
|
||||
@@ -37,12 +37,18 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
|
||||
public class AdditionalHeadersHttpServletRequestWrapper extends HttpServletRequestWrapper
|
||||
public class AdminConsoleHttpServletRequestWrapper extends HttpServletRequestWrapper
|
||||
{
|
||||
private final Map<String, String> additionalHeaders;
|
||||
private final HttpServletRequest wrappedRequest;
|
||||
|
||||
public AdditionalHeadersHttpServletRequestWrapper(Map<String, String> additionalHeaders, HttpServletRequest request)
|
||||
/**
|
||||
* Constructs a request object wrapping the given request.
|
||||
*
|
||||
* @param request the request to wrap
|
||||
* @throws IllegalArgumentException if the request is null
|
||||
*/
|
||||
public AdminConsoleHttpServletRequestWrapper(Map<String, String> additionalHeaders, HttpServletRequest request)
|
||||
{
|
||||
super(request);
|
||||
PropertyCheck.mandatory(this, "additionalHeaders", additionalHeaders);
|
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -23,7 +23,7 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.authentication;
|
||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
||||
|
||||
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant.authorizationCode;
|
||||
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceMetadataKey.SCOPES_SUPPORTED;
|
||||
@@ -32,16 +32,27 @@ import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.Scope;
|
||||
import com.nimbusds.oauth2.sdk.id.Identifier;
|
||||
import com.nimbusds.oauth2.sdk.id.State;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator;
|
||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -49,35 +60,29 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
|
||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||
|
||||
public abstract class AbstractIdentityServiceAuthenticator implements ExternalUserAuthenticator
|
||||
/**
|
||||
* An {@link AdminConsoleAuthenticator} implementation to extract an externally authenticated user ID
|
||||
* or to initiate the OIDC authorization code flow.
|
||||
*/
|
||||
public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAuthenticator, ActivateableBean
|
||||
{
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIdentityServiceAuthenticator.class);
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(IdentityServiceAdminConsoleAuthenticator.class);
|
||||
|
||||
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
|
||||
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
|
||||
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
|
||||
private static final Set<String> SCOPES = Set.of("openid", "profile", "email", "offline_access");
|
||||
|
||||
protected IdentityServiceConfig identityServiceConfig;
|
||||
protected IdentityServiceFacade identityServiceFacade;
|
||||
protected AdminAuthenticationCookiesService cookiesService;
|
||||
protected RemoteUserMapper remoteUserMapper;
|
||||
|
||||
protected abstract String getConfiguredRedirectPath();
|
||||
|
||||
protected abstract Set<String> getConfiguredScopes();
|
||||
private IdentityServiceConfig identityServiceConfig;
|
||||
private IdentityServiceFacade identityServiceFacade;
|
||||
private AdminConsoleAuthenticationCookiesService cookiesService;
|
||||
private RemoteUserMapper remoteUserMapper;
|
||||
private boolean isEnabled;
|
||||
|
||||
@Override
|
||||
public String getUserId(HttpServletRequest request, HttpServletResponse response)
|
||||
public String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
// Try to extract username from the authorization header
|
||||
String username = remoteUserMapper.getRemoteUser(request);
|
||||
if (username != null)
|
||||
{
|
||||
@@ -104,12 +109,16 @@ public abstract class AbstractIdentityServiceAuthenticator implements ExternalUs
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpServletRequest wrappedRequest = newRequestWrapper(Map.of("Authorization", "Bearer " + bearerToken), request);
|
||||
return remoteUserMapper.getRemoteUser(wrappedRequest);
|
||||
return remoteUserMapper.getRemoteUser(decorateBearerHeader(bearerToken, request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
respondWithAuthChallenge(request, response);
|
||||
}
|
||||
|
||||
private void respondWithAuthChallenge(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -117,8 +126,7 @@ public abstract class AbstractIdentityServiceAuthenticator implements ExternalUs
|
||||
{
|
||||
LOGGER.debug("Responding with the authentication challenge");
|
||||
}
|
||||
String authenticationRequest = buildAuthRequestUrl(request);
|
||||
response.sendRedirect(authenticationRequest);
|
||||
response.sendRedirect(getAuthenticationRequest(request));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
@@ -127,110 +135,36 @@ public abstract class AbstractIdentityServiceAuthenticator implements ExternalUs
|
||||
}
|
||||
}
|
||||
|
||||
protected String getRedirectUri(String requestURL)
|
||||
private String retrieveTokenUsingAuthCode(HttpServletRequest request, HttpServletResponse response, String code)
|
||||
{
|
||||
return buildRedirectUri(requestURL, getConfiguredRedirectPath());
|
||||
}
|
||||
|
||||
public String buildAuthRequestUrl(HttpServletRequest request)
|
||||
{
|
||||
ClientRegistration clientRegistration = identityServiceFacade.getClientRegistration();
|
||||
State state = new State();
|
||||
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails()
|
||||
.getAuthorizationUri())
|
||||
.queryParam("client_id", clientRegistration.getClientId())
|
||||
.queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString()))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", String.join("+", getConfiguredScopes(clientRegistration)))
|
||||
.queryParam("state", state.toString());
|
||||
|
||||
if (StringUtils.isNotBlank(identityServiceConfig.getAudience()))
|
||||
String bearerToken = null;
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
builder.queryParam("audience", identityServiceConfig.getAudience());
|
||||
LOGGER.debug("Retrieving a response using the Authorization Code at the Token Endpoint");
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
private Set<String> getConfiguredScopes(ClientRegistration clientRegistration)
|
||||
{
|
||||
return Optional.ofNullable(clientRegistration.getProviderDetails())
|
||||
.map(ProviderDetails::getConfigurationMetadata)
|
||||
.map(metadata -> metadata.get(SCOPES_SUPPORTED.getValue()))
|
||||
.filter(Scope.class::isInstance)
|
||||
.map(Scope.class::cast)
|
||||
.map(this::getSupportedScopes)
|
||||
.orElse(clientRegistration.getScopes());
|
||||
}
|
||||
|
||||
private Set<String> getSupportedScopes(Scope scopes)
|
||||
{
|
||||
Set<String> configuredScopes = getConfiguredScopes();
|
||||
return scopes.stream()
|
||||
.map(Identifier::getValue)
|
||||
.filter(configuredScopes::contains)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
protected String buildRedirectUri(String requestURL, String overridePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
URI originalUri = new URI(requestURL);
|
||||
String path = overridePath != null ? overridePath : originalUri.getPath();
|
||||
|
||||
URI redirectUri = new URI(
|
||||
originalUri.getScheme(),
|
||||
originalUri.getAuthority(),
|
||||
path,
|
||||
originalUri.getQuery(),
|
||||
originalUri.getFragment());
|
||||
|
||||
return redirectUri.toASCIIString();
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
LOGGER.error("Redirect URI construction failed: {}", e.getMessage(), e);
|
||||
throw new AuthenticationException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void challenge(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
try
|
||||
{
|
||||
response.sendRedirect(buildAuthRequestUrl(request));
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new AuthenticationException("Auth redirect failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String retrieveTokenUsingAuthCode(HttpServletRequest request, HttpServletResponse response, String code)
|
||||
{
|
||||
try
|
||||
{
|
||||
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(authorizationCode(code, getRedirectUri(request.getRequestURL()
|
||||
.toString())));
|
||||
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
|
||||
authorizationCode(code, request.getRequestURL().toString()));
|
||||
addCookies(response, accessTokenAuthorization);
|
||||
return accessTokenAuthorization.getAccessToken()
|
||||
.getTokenValue();
|
||||
bearerToken = accessTokenAuthorization.getAccessToken().getTokenValue();
|
||||
}
|
||||
catch (AuthorizationException exception)
|
||||
{
|
||||
LOGGER.warn("Error while trying to retrieve token using Authorization Code: {}", exception.getMessage());
|
||||
return null;
|
||||
if (LOGGER.isWarnEnabled())
|
||||
{
|
||||
LOGGER.warn(
|
||||
"Error while trying to retrieve a response using the Authorization Code at the Token Endpoint: {}",
|
||||
exception.getMessage());
|
||||
}
|
||||
}
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
protected String refreshTokenIfNeeded(HttpServletRequest request, HttpServletResponse response, String bearerToken)
|
||||
private String refreshTokenIfNeeded(HttpServletRequest request, HttpServletResponse response, String bearerToken)
|
||||
{
|
||||
String refreshToken = cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request);
|
||||
String authTokenExpiration = cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request);
|
||||
|
||||
try
|
||||
{
|
||||
if (isAuthTokenExpired(authTokenExpiration))
|
||||
@@ -242,74 +176,144 @@ public abstract class AbstractIdentityServiceAuthenticator implements ExternalUs
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Token refresh failed: {}", e.getMessage());
|
||||
LOGGER.debug("Error while trying to refresh Auth Token: {}", e.getMessage());
|
||||
}
|
||||
bearerToken = null;
|
||||
resetCookies(response);
|
||||
}
|
||||
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
private static boolean isAuthTokenExpired(String authTokenExpiration)
|
||||
private void addCookies(HttpServletResponse response, AccessTokenAuthorization accessTokenAuthorization)
|
||||
{
|
||||
return authTokenExpiration == null || Instant.now()
|
||||
.compareTo(Instant.ofEpochMilli(Long.parseLong(authTokenExpiration))) >= 0;
|
||||
}
|
||||
|
||||
private String refreshAuthToken(String refreshToken, HttpServletResponse response)
|
||||
{
|
||||
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(AuthorizationGrant.refreshToken(refreshToken));
|
||||
if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null)
|
||||
{
|
||||
throw new AuthenticationException("Refresh token response is invalid.");
|
||||
}
|
||||
addCookies(response, accessTokenAuthorization);
|
||||
return accessTokenAuthorization.getAccessToken()
|
||||
.getTokenValue();
|
||||
|
||||
}
|
||||
|
||||
protected void addCookies(HttpServletResponse response, AccessTokenAuthorization accessTokenAuthorization)
|
||||
{
|
||||
cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken()
|
||||
.getTokenValue(), response);
|
||||
cookiesService.addCookie(ALFRESCO_TOKEN_EXPIRATION, String.valueOf(accessTokenAuthorization.getAccessToken()
|
||||
.getExpiresAt()
|
||||
.toEpochMilli()), response);
|
||||
cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken().getTokenValue(), response);
|
||||
cookiesService.addCookie(ALFRESCO_TOKEN_EXPIRATION, String.valueOf(
|
||||
accessTokenAuthorization.getAccessToken().getExpiresAt().toEpochMilli()), response);
|
||||
cookiesService.addCookie(ALFRESCO_REFRESH_TOKEN, accessTokenAuthorization.getRefreshTokenValue(), response);
|
||||
}
|
||||
|
||||
protected void resetCookies(HttpServletResponse response)
|
||||
private String getAuthenticationRequest(HttpServletRequest request)
|
||||
{
|
||||
ClientRegistration clientRegistration = identityServiceFacade.getClientRegistration();
|
||||
State state = new State();
|
||||
|
||||
UriComponentsBuilder authRequestBuilder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getAuthorizationUri())
|
||||
.queryParam("client_id", clientRegistration.getClientId())
|
||||
.queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString()))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", String.join("+", getScopes(clientRegistration)))
|
||||
.queryParam("state", state.toString());
|
||||
|
||||
if(StringUtils.isNotBlank(identityServiceConfig.getAudience()))
|
||||
{
|
||||
authRequestBuilder.queryParam("audience", identityServiceConfig.getAudience());
|
||||
}
|
||||
|
||||
return authRequestBuilder.build().toUriString();
|
||||
}
|
||||
|
||||
private Set<String> getScopes(ClientRegistration clientRegistration)
|
||||
{
|
||||
return Optional.ofNullable(clientRegistration.getProviderDetails())
|
||||
.map(ProviderDetails::getConfigurationMetadata)
|
||||
.map(metadata -> metadata.get(SCOPES_SUPPORTED.getValue()))
|
||||
.filter(Scope.class::isInstance)
|
||||
.map(Scope.class::cast)
|
||||
.map(this::getSupportedScopes)
|
||||
.orElse(clientRegistration.getScopes());
|
||||
}
|
||||
|
||||
private Set<String> getSupportedScopes(Scope scopes)
|
||||
{
|
||||
return scopes.stream()
|
||||
.filter(scope -> SCOPES.contains(scope.getValue()))
|
||||
.map(Identifier::getValue)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private String getRedirectUri(String requestURL)
|
||||
{
|
||||
try
|
||||
{
|
||||
URI originalUri = new URI(requestURL);
|
||||
URI redirectUri = new URI(originalUri.getScheme(), originalUri.getAuthority(), identityServiceConfig.getAdminConsoleRedirectPath(), originalUri.getQuery(), originalUri.getFragment());
|
||||
return redirectUri.toASCIIString();
|
||||
}
|
||||
catch (URISyntaxException e)
|
||||
{
|
||||
LOGGER.error("Error while trying to get the redirect URI and respond with the authentication challenge: {}", e.getMessage(), e);
|
||||
throw new AuthenticationException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetCookies(HttpServletResponse response)
|
||||
{
|
||||
cookiesService.resetCookie(ALFRESCO_TOKEN_EXPIRATION, response);
|
||||
cookiesService.resetCookie(ALFRESCO_ACCESS_TOKEN, response);
|
||||
cookiesService.resetCookie(ALFRESCO_REFRESH_TOKEN, response);
|
||||
}
|
||||
|
||||
protected HttpServletRequest newRequestWrapper(Map<String, String> headers, HttpServletRequest request)
|
||||
private String refreshAuthToken(String refreshToken, HttpServletResponse response)
|
||||
{
|
||||
return new AdditionalHeadersHttpServletRequestWrapper(headers, request);
|
||||
AccessTokenAuthorization accessTokenAuthorization = doRefreshAuthToken(refreshToken);
|
||||
addCookies(response, accessTokenAuthorization);
|
||||
return accessTokenAuthorization.getAccessToken().getTokenValue();
|
||||
}
|
||||
|
||||
// Setters
|
||||
public void setIdentityServiceConfig(IdentityServiceConfig config)
|
||||
private AccessTokenAuthorization doRefreshAuthToken(String refreshToken)
|
||||
{
|
||||
this.identityServiceConfig = config;
|
||||
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
|
||||
AuthorizationGrant.refreshToken(refreshToken));
|
||||
if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null)
|
||||
{
|
||||
throw new AuthenticationException("AccessTokenResponse is null or empty");
|
||||
}
|
||||
return accessTokenAuthorization;
|
||||
}
|
||||
|
||||
public void setIdentityServiceFacade(IdentityServiceFacade facade)
|
||||
private static boolean isAuthTokenExpired(String authTokenExpiration)
|
||||
{
|
||||
this.identityServiceFacade = facade;
|
||||
return Instant.now().compareTo(Instant.ofEpochMilli(Long.parseLong(authTokenExpiration))) >= 0;
|
||||
}
|
||||
|
||||
public void setCookiesService(AdminAuthenticationCookiesService service)
|
||||
private HttpServletRequest decorateBearerHeader(String authToken, HttpServletRequest servletRequest)
|
||||
{
|
||||
this.cookiesService = service;
|
||||
Map<String, String> additionalHeaders = new HashMap<>();
|
||||
additionalHeaders.put("Authorization", "Bearer " + authToken);
|
||||
return new AdminConsoleHttpServletRequestWrapper(additionalHeaders, servletRequest);
|
||||
}
|
||||
|
||||
public void setRemoteUserMapper(RemoteUserMapper mapper)
|
||||
public void setIdentityServiceFacade(
|
||||
IdentityServiceFacade identityServiceFacade)
|
||||
{
|
||||
this.remoteUserMapper = mapper;
|
||||
this.identityServiceFacade = identityServiceFacade;
|
||||
}
|
||||
|
||||
public void setRemoteUserMapper(RemoteUserMapper remoteUserMapper)
|
||||
{
|
||||
this.remoteUserMapper = remoteUserMapper;
|
||||
}
|
||||
|
||||
public void setCookiesService(
|
||||
AdminConsoleAuthenticationCookiesService cookiesService)
|
||||
{
|
||||
this.cookiesService = cookiesService;
|
||||
}
|
||||
|
||||
public void setIdentityServiceConfig(
|
||||
IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
this.identityServiceConfig = identityServiceConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
public void setActive(boolean isEnabled)
|
||||
{
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.authentication.admin;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
|
||||
import org.alfresco.repo.security.authentication.identityservice.authentication.AbstractIdentityServiceAuthenticator;
|
||||
|
||||
/**
|
||||
* An {@link ExternalUserAuthenticator} implementation to extract an externally authenticated user ID or to initiate the OIDC authorization code flow.
|
||||
*/
|
||||
public class IdentityServiceAdminConsoleAuthenticator extends AbstractIdentityServiceAuthenticator
|
||||
implements ExternalUserAuthenticator, ActivateableBean
|
||||
{
|
||||
private boolean isEnabled;
|
||||
|
||||
@Override
|
||||
protected Set<String> getConfiguredScopes()
|
||||
{
|
||||
return identityServiceConfig.getAdminConsoleScopes();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getConfiguredRedirectPath()
|
||||
{
|
||||
return identityServiceConfig.getAdminConsoleRedirectPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
public void setActive(boolean isEnabled)
|
||||
{
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user