Compare commits

...

31 Commits

Author SHA1 Message Date
alfresco-build
7bb4ab869a [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-20 11:23:16 +00:00
alfresco-build
3a16441521 [maven-release-plugin][skip ci] prepare release 25.1.2.5 2025-08-20 11:23:13 +00:00
SatyamSah5
2a13c1b496 ACS-9991 Allow Content and Metadata extract even if thumbnails are di… (#3536) 2025-08-20 16:08:49 +05:30
alfresco-build
8d802f8e3b [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-25 09:24:34 +00:00
alfresco-build
5c74de837a [maven-release-plugin][skip ci] prepare release 25.1.2.4 2025-06-25 09:24:32 +00:00
KushalBanik
992c3d9e90 [PRODSEC-10187] commons-beanutils bumped up to 1.11.0 for 25.1 HotFix (#3400) 2025-06-25 14:07:59 +05:30
alfresco-build
e06a69e867 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-09 10:21:27 +00:00
alfresco-build
fe06ef712a [maven-release-plugin][skip ci] prepare release 25.1.2.3 2025-06-09 10:21:25 +00:00
Gerard Olenski
b88ef90029 ACS-9578 Improve stability in AddToHoldsBulkV1Tests (#3355)
(cherry picked from commit d163410e3d)
2025-06-09 11:26:05 +02:00
alfresco-build
fd57312452 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-06 10:16:36 +00:00
alfresco-build
a5a4ae7e97 [maven-release-plugin][skip ci] prepare release 25.1.2.2 2025-06-06 10:16:34 +00:00
Gerard Olenski
fbce7372ac ACS-9676 Fix unstable AddToHoldsBulkV1Tests (#3380)
Co-authored-by: Belal Ansari <belal.ansari@hyland.com>
2025-06-06 11:29:38 +02:00
alfresco-build
e1e694227a [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-28 18:21:05 +00:00
alfresco-build
5429dbbb1b [maven-release-plugin][skip ci] prepare release 25.1.2.1 2025-05-28 18:21:03 +00:00
Eva Vasques
4eb32a7423 Prepare version 25.1.2 (#3370) 2025-05-28 18:10:56 +01:00
alfresco-build
712a5f5013 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-27 15:40:45 +00:00
alfresco-build
cb825f0b8b [maven-release-plugin][skip ci] prepare release 25.1.1.5 2025-05-27 15:40:43 +00:00
Eva Vasques
c43eaaef7b [skip tests] Release and Update Downstream 2025-05-27 16:37:19 +01:00
alfresco-build
8dc1ed62ab [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-14 14:11:33 +00:00
alfresco-build
b4e52bd2ab [maven-release-plugin][skip ci] prepare release 25.1.1.4 2025-05-14 14:11:31 +00:00
Damian Ujma
2b38242eba ACS-9416 Backport ACS-9414 Enhance the Identity Provider configuration ( #3263) (#3342) 2025-05-14 15:25:45 +02:00
alfresco-build
c755cb5d6f [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-10 11:07:04 +00:00
alfresco-build
a9ea867a73 [maven-release-plugin][skip ci] prepare release 25.1.1.3 2025-04-10 11:07:01 +00:00
Belal Ansari
4094dab7e1 [MNT-24891] backport changes - Skip actionContext to classify as an adhoc property in add-features action logic (#3307)
Co-authored-by: Kacper Magdziarz <kacper.magdziarz@hyland.com>
2025-04-10 12:14:50 +02:00
alfresco-build
1c5833c0e2 [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-04 11:24:54 +00:00
alfresco-build
2e1f943390 [maven-release-plugin][skip ci] prepare release 25.1.1.2 2025-04-04 11:24:52 +00:00
Kacper Magdziarz
71c6f21687 [ACS-9490] Backport: Use FixedThreadPool for ExecutorService (#3301) (#3302) 2025-04-04 12:42:49 +02:00
alfresco-build
a48d7c2cdd [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-02 09:14:04 +00:00
alfresco-build
e186f9737a [maven-release-plugin][skip ci] prepare release 25.1.1.1 2025-04-02 09:14:01 +00:00
cezary-witkowski
97e71f77f9 [MNT-24937] Fix EventTableOutbox messages flooding the logs on bootstrap failure (#3286) (#3296)
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
2025-04-02 10:29:34 +02:00
Kacper Magdziarz
38d61e9cf6 Creating hotfix branch release/25.1 for 25.1.0 ACS release [skip ci] 2025-03-17 13:46:29 +01:00
64 changed files with 3899 additions and 3176 deletions

View File

@@ -44,14 +44,10 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- id: changed-files - uses: Alfresco/alfresco-build-tools/.github/actions/pre-commit@v8.16.0
uses: Alfresco/alfresco-build-tools/.github/actions/github-list-changes@v8.13.0
with:
write-list-to-env: true
- uses: Alfresco/alfresco-build-tools/.github/actions/pre-commit@v8.13.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Prepare maven cache and check compilation" - name: "Prepare maven cache and check compilation"
@@ -69,12 +65,12 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- uses: Alfresco/alfresco-build-tools/.github/actions/veracode@v8.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/veracode@v8.16.0
continue-on-error: true continue-on-error: true
with: with:
srcclr-api-token: ${{ secrets.SRCCLR_API_TOKEN }} srcclr-api-token: ${{ secrets.SRCCLR_API_TOKEN }}
@@ -92,10 +88,10 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/github-download-file@v8.16.0
with: with:
token: ${{ secrets.BOT_GITHUB_TOKEN }} token: ${{ secrets.BOT_GITHUB_TOKEN }}
repository: "Alfresco/veracode-baseline-archive" repository: "Alfresco/veracode-baseline-archive"
@@ -148,10 +144,10 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') && !contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force]') !contains(github.event.head_commit.message, '[force]')
steps: steps:
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- uses: Alfresco/ya-pmd-scan@v4.1.0 - uses: Alfresco/ya-pmd-scan@v4.3.0
with: with:
classpath-build-command: "mvn test-compile -ntp -Pags -pl \"-:alfresco-community-repo-docker\"" classpath-build-command: "mvn test-compile -ntp -Pags -pl \"-:alfresco-community-repo-docker\""
@@ -181,14 +177,14 @@ jobs:
testAttributes: "-Dtest=AllMmtUnitTestSuite" testAttributes: "-Dtest=AllMmtUnitTestSuite"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testModule }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testModule }}
@@ -219,7 +215,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -261,9 +257,9 @@ jobs:
REQUIRES_INSTALLED_ARTIFACTS: true REQUIRES_INSTALLED_ARTIFACTS: true
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Build" - name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }} timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: | run: |
@@ -276,7 +272,7 @@ jobs:
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }}
@@ -307,7 +303,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -340,9 +336,9 @@ jobs:
version: ['10.5', '10.6'] version: ['10.5', '10.6']
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: Run MariaDB ${{ matrix.version }} database - name: Run MariaDB ${{ matrix.version }} database
@@ -351,7 +347,7 @@ jobs:
MARIADB_VERSION: ${{ matrix.version }} MARIADB_VERSION: ${{ matrix.version }}
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.version }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.version }}
@@ -382,7 +378,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -411,9 +407,9 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Run MariaDB 10.11 database" - name: "Run MariaDB 10.11 database"
@@ -422,7 +418,7 @@ jobs:
MARIADB_VERSION: 10.11 MARIADB_VERSION: 10.11
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
@@ -453,7 +449,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -482,9 +478,9 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Run MySQL 8 database" - name: "Run MySQL 8 database"
@@ -493,7 +489,7 @@ jobs:
MYSQL_VERSION: 8 MYSQL_VERSION: 8
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
@@ -524,7 +520,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -552,9 +548,9 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Run PostgreSQL 14.15 database" - name: "Run PostgreSQL 14.15 database"
@@ -563,7 +559,7 @@ jobs:
POSTGRES_VERSION: 14.15 POSTGRES_VERSION: 14.15
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
@@ -594,7 +590,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -622,9 +618,9 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Run PostgreSQL 15.10 database" - name: "Run PostgreSQL 15.10 database"
@@ -633,7 +629,7 @@ jobs:
POSTGRES_VERSION: 15.10 POSTGRES_VERSION: 15.10
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
@@ -664,7 +660,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -692,9 +688,9 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Run PostgreSQL 16.6 database" - name: "Run PostgreSQL 16.6 database"
@@ -703,7 +699,7 @@ jobs:
POSTGRES_VERSION: 16.6 POSTGRES_VERSION: 16.6
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
@@ -734,7 +730,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -760,16 +756,16 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Run ActiveMQ" - name: "Run ActiveMQ"
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile activemq up -d run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile activemq up -d
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
@@ -800,7 +796,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -860,9 +856,9 @@ jobs:
mvn-options: '-Dencryption.ssl.keystore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.truststore' mvn-options: '-Dencryption.ssl.keystore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.truststore'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Set transformers tag" - name: "Set transformers tag"
@@ -885,7 +881,7 @@ jobs:
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }} ${{ matrix.idp }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }} ${{ matrix.idp }}
@@ -916,7 +912,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -974,9 +970,9 @@ jobs:
REQUIRES_LOCAL_IMAGES: true REQUIRES_LOCAL_IMAGES: true
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Build" - name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }} timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: | run: |
@@ -992,7 +988,7 @@ jobs:
run: mvn install -pl :alfresco-community-repo-integration-test -am -DskipTests -Pall-tas-tests run: mvn install -pl :alfresco-community-repo-integration-test -am -DskipTests -Pall-tas-tests
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.test-name }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.test-name }}
@@ -1030,7 +1026,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.tests.outcome }} tests-outcome: ${{ steps.tests.outcome }}
@@ -1056,16 +1052,16 @@ jobs:
!contains(github.event.head_commit.message, '[force') !contains(github.event.head_commit.message, '[force')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Init" - name: "Init"
run: bash ./scripts/ci/init.sh run: bash ./scripts/ci/init.sh
- name: "Run Postgres 16.6 database" - name: "Run Postgres 16.6 database"
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile postgres up -d run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile postgres up -d
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
@@ -1096,7 +1092,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -1130,9 +1126,9 @@ jobs:
REQUIRES_INSTALLED_ARTIFACTS: true REQUIRES_INSTALLED_ARTIFACTS: true
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Build" - name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }} timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: | run: |
@@ -1140,7 +1136,7 @@ jobs:
bash ./scripts/ci/build.sh bash ./scripts/ci/build.sh
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (PostgreSQL) ${{ matrix.test-name }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (PostgreSQL) ${{ matrix.test-name }}
@@ -1176,9 +1172,9 @@ jobs:
REQUIRES_INSTALLED_ARTIFACTS: true REQUIRES_INSTALLED_ARTIFACTS: true
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Build" - name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }} timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: | run: |
@@ -1186,7 +1182,7 @@ jobs:
bash ./scripts/ci/build.sh bash ./scripts/ci/build.sh
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (MySQL) ${{ matrix.test-name }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (MySQL) ${{ matrix.test-name }}
@@ -1218,9 +1214,9 @@ jobs:
REQUIRES_LOCAL_IMAGES: true REQUIRES_LOCAL_IMAGES: true
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Build" - name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }} timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: | run: |
@@ -1234,7 +1230,7 @@ jobs:
mvn -B install -pl :alfresco-governance-services-automation-community-rest-api -am -Pags -Pall-tas-tests -DskipTests mvn -B install -pl :alfresco-governance-services-automation-community-rest-api -am -Pags -Pall-tas-tests -DskipTests
- name: "Prepare Report Portal" - name: "Prepare Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
id: rp-prepare id: rp-prepare
with: with:
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
@@ -1266,7 +1262,7 @@ jobs:
continue-on-error: true continue-on-error: true
- name: "Summarize Report Portal" - name: "Summarize Report Portal"
if: github.ref_name == 'master' if: github.ref_name == 'master'
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.13.0 uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
id: rp-summarize id: rp-summarize
with: with:
tests-outcome: ${{ steps.run-tests.outcome }} tests-outcome: ${{ steps.run-tests.outcome }}
@@ -1308,9 +1304,9 @@ jobs:
!contains(github.event.head_commit.message, '[force]') !contains(github.event.head_commit.message, '[force]')
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.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.13.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.13.0 - uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
- name: "Build" - name: "Build"
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }} timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
run: | run: |

View File

@@ -133,21 +133,21 @@
"filename": ".github/workflows/ci.yml", "filename": ".github/workflows/ci.yml",
"hashed_secret": "b86dc2f033a63f2b7b9e7d270ab806d2910d7572", "hashed_secret": "b86dc2f033a63f2b7b9e7d270ab806d2910d7572",
"is_verified": false, "is_verified": false,
"line_number": 299 "line_number": 295
}, },
{ {
"type": "Secret Keyword", "type": "Secret Keyword",
"filename": ".github/workflows/ci.yml", "filename": ".github/workflows/ci.yml",
"hashed_secret": "1bfb0e20f886150ba59b853bcd49dea893e00966", "hashed_secret": "1bfb0e20f886150ba59b853bcd49dea893e00966",
"is_verified": false, "is_verified": false,
"line_number": 374 "line_number": 370
}, },
{ {
"type": "Secret Keyword", "type": "Secret Keyword",
"filename": ".github/workflows/ci.yml", "filename": ".github/workflows/ci.yml",
"hashed_secret": "128f14373ccfaff49e3664045d3a11b50cbb7b39", "hashed_secret": "128f14373ccfaff49e3664045d3a11b50cbb7b39",
"is_verified": false, "is_verified": false,
"line_number": 908 "line_number": 904
} }
], ],
".github/workflows/master_release.yml": [ ".github/workflows/master_release.yml": [
@@ -1607,7 +1607,7 @@
"filename": "repository/src/test/java/org/alfresco/repo/security/authentication/identityservice/SpringBasedIdentityServiceFacadeUnitTest.java", "filename": "repository/src/test/java/org/alfresco/repo/security/authentication/identityservice/SpringBasedIdentityServiceFacadeUnitTest.java",
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
"is_verified": false, "is_verified": false,
"line_number": 46, "line_number": 48,
"is_secret": false "is_secret": false
} }
], ],
@@ -1868,5 +1868,5 @@
} }
] ]
}, },
"generated_at": "2025-02-26T15:13:52Z" "generated_at": "2025-05-13T13:17:41Z"
} }

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId> <artifactId>alfresco-community-repo-amps</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<modules> <modules>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId> <artifactId>alfresco-governance-services-community-parent</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<modules> <modules>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-automation-community-repo</artifactId> <artifactId>alfresco-governance-services-automation-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<build> <build>

View File

@@ -131,6 +131,16 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
.until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination() .until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination()
.getTotalItems() == NUMBER_OF_FILES); .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);
holdBulkOperation = HoldBulkOperation.builder() holdBulkOperation = HoldBulkOperation.builder()
.query(queryReq) .query(queryReq)
.op(HoldBulkOperationType.ADD).build(); .op(HoldBulkOperationType.ADD).build();

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId> <artifactId>alfresco-governance-services-community-parent</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<modules> <modules>

View File

@@ -8,7 +8,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId> <artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<properties> <properties>

View File

@@ -5,7 +5,7 @@
# Version label # Version label
version.major=25 version.major=25
version.minor=1 version.minor=1
version.revision=0 version.revision=1
version.label= version.label=
# Edition label # Edition label

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-repo-parent</artifactId> <artifactId>alfresco-governance-services-community-repo-parent</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<build> <build>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<modules> <modules>

View File

@@ -8,7 +8,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId> <artifactId>alfresco-community-repo-amps</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<properties> <properties>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<dependencies> <dependencies>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<properties> <properties>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<dependencies> <dependencies>

View File

@@ -9,6 +9,6 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId> <artifactId>alfresco-community-repo-packaging</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
</project> </project>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId> <artifactId>alfresco-community-repo-packaging</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<properties> <properties>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<modules> <modules>

View File

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId> <artifactId>alfresco-community-repo-packaging</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<modules> <modules>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId> <artifactId>alfresco-community-repo-tests</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<organization> <organization>

View File

@@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId> <artifactId>alfresco-community-repo-tests</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<developers> <developers>

View File

@@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId> <artifactId>alfresco-community-repo-tests</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<developers> <developers>

View File

@@ -8,7 +8,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId> <artifactId>alfresco-community-repo-tests</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<properties> <properties>

View File

@@ -9,7 +9,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId> <artifactId>alfresco-community-repo-tests</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<developers> <developers>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId> <artifactId>alfresco-community-repo-packaging</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<properties> <properties>

View File

@@ -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"> <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> <modelVersion>4.0.0</modelVersion>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name> <name>Alfresco Community Repo Parent</name>
@@ -25,7 +25,7 @@
<properties> <properties>
<acs.version.major>25</acs.version.major> <acs.version.major>25</acs.version.major>
<acs.version.minor>1</acs.version.minor> <acs.version.minor>1</acs.version.minor>
<acs.version.revision>0</acs.version.revision> <acs.version.revision>2</acs.version.revision>
<acs.version.label /> <acs.version.label />
<amp.min.version>${acs.version.major}.0.0</amp.min.version> <amp.min.version>${acs.version.major}.0.0</amp.min.version>
@@ -155,7 +155,7 @@
<connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection> <connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection>
<developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection> <developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection>
<url>https://github.com/Alfresco/alfresco-community-repo</url> <url>https://github.com/Alfresco/alfresco-community-repo</url>
<tag>25.1.0.71</tag> <tag>HEAD</tag>
</scm> </scm>
<distributionManagement> <distributionManagement>
@@ -410,7 +410,7 @@
<dependency> <dependency>
<groupId>commons-beanutils</groupId> <groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId> <artifactId>commons-beanutils</artifactId>
<version>1.9.4</version> <version>1.11.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<dependencies> <dependencies>

View File

@@ -7,7 +7,7 @@
<parent> <parent>
<groupId>org.alfresco</groupId> <groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId> <artifactId>alfresco-community-repo</artifactId>
<version>25.1.0.71</version> <version>25.1.2.6-SNAPSHOT</version>
</parent> </parent>
<dependencies> <dependencies>

View File

@@ -2,93 +2,96 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.action.executer; package org.alfresco.repo.action.executer;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.action.access.ActionAccessRestriction;
import org.alfresco.service.cmr.action.Action; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition; import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
/**
* Add features action executor implementation. /**
* * Add features action executor implementation.
* @author Roy Wetherall *
*/ * @author Roy Wetherall
public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase */
{ public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase
/** {
* Action constants /**
*/ * Action constants
public static final String NAME = "add-features"; */
public static final String PARAM_ASPECT_NAME = "aspect-name"; public static final String NAME = "add-features";
public static final String PARAM_CONSTRAINT = "ac-aspects"; public static final String PARAM_ASPECT_NAME = "aspect-name";
public static final String PARAM_CONSTRAINT = "ac-aspects";
/**
* The node service /**
*/ * The node service
private NodeService nodeService; */
private NodeService nodeService;
/** Transaction Service, used for retrying operations */
private TransactionService transactionService; /** Transaction Service, used for retrying operations */
private TransactionService transactionService;
/**
* Set the node service /**
* * Set the node service
* @param nodeService the node service *
*/ * @param nodeService
public void setNodeService(NodeService nodeService) * the node service
{ */
this.nodeService = nodeService; public void setNodeService(NodeService nodeService)
} {
this.nodeService = nodeService;
/** }
* Set the transaction service
* /**
* @param transactionService the transaction service * Set the transaction service
*/ *
public void setTransactionService(TransactionService transactionService) * @param transactionService
{ * the transaction service
this.transactionService = transactionService; */
} public void setTransactionService(TransactionService transactionService)
{
/** this.transactionService = transactionService;
* Adhoc properties are allowed for this executor }
*/
@Override /**
protected boolean getAdhocPropertiesAllowed() * Adhoc properties are allowed for this executor
{ */
return true; @Override
protected boolean getAdhocPropertiesAllowed()
{
return true;
} }
/** /**
@@ -96,55 +99,61 @@ public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase
*/ */
public void executeImpl(final Action ruleAction, final NodeRef actionedUponNodeRef) public void executeImpl(final Action ruleAction, final NodeRef actionedUponNodeRef)
{ {
if (this.nodeService.exists(actionedUponNodeRef)) if (this.nodeService.exists(actionedUponNodeRef))
{ {
transactionService.getRetryingTransactionHelper().doInTransaction( transactionService.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback<Void>() new RetryingTransactionCallback<Void>() {
{ public Void execute() throws Throwable
public Void execute() throws Throwable {
{ Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
Map<QName, Serializable> properties = new HashMap<QName, Serializable>(); QName aspectQName = null;
QName aspectQName = null;
if (!nodeService.exists(actionedUponNodeRef))
if(! nodeService.exists(actionedUponNodeRef)) {
{ // Node has gone away, skip
// Node has gone away, skip return null;
return null; }
}
// Build the aspect details
// Build the aspect details Map<String, Serializable> paramValues = ruleAction.getParameterValues();
Map<String, Serializable> paramValues = ruleAction.getParameterValues(); removeActionContextParameter(paramValues);
for (Map.Entry<String, Serializable> entry : paramValues.entrySet()) for (Map.Entry<String, Serializable> entry : paramValues.entrySet())
{ {
if (entry.getKey().equals(PARAM_ASPECT_NAME) == true) if (entry.getKey().equals(PARAM_ASPECT_NAME) == true)
{ {
aspectQName = (QName)entry.getValue(); aspectQName = (QName) entry.getValue();
} }
else else
{ {
// Must be an adhoc property // Must be an adhoc property
QName propertyQName = QName.createQName(entry.getKey()); QName propertyQName = QName.createQName(entry.getKey());
Serializable propertyValue = entry.getValue(); Serializable propertyValue = entry.getValue();
properties.put(propertyQName, propertyValue); properties.put(propertyQName, propertyValue);
} }
} }
// Add the aspect // Add the aspect
nodeService.addAspect(actionedUponNodeRef, aspectQName, properties); nodeService.addAspect(actionedUponNodeRef, aspectQName, properties);
return null; return null;
} }
} });
); }
} }
}
/**
/** * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List) */
*/ @Override
@Override protected void addParameterDefinitions(List<ParameterDefinition> paramList)
protected void addParameterDefinitions(List<ParameterDefinition> paramList) {
{ paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT_NAME), false, "ac-aspects"));
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);
}
}

View File

@@ -542,10 +542,7 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
@Override @Override
protected void onShutdown(ApplicationEvent applicationEvent) protected void onShutdown(ApplicationEvent applicationEvent)
{ {
if (eventSender != null) // NOOP
{
eventSender.destroy();
}
} }
protected class EventTransactionListener extends TransactionListenerAdapter protected class EventTransactionListener extends TransactionListenerAdapter
@@ -819,4 +816,4 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
return peerAssocs; return peerAssocs;
} }
} }
} }

View File

@@ -52,7 +52,7 @@ public interface EventSender
} }
/** /**
* It's called when the application context is closing, allowing {@link org.alfresco.repo.event2.EventGenerator} to perform cleanup operations. * It's called when the bean instance is destroyed, allowing to perform cleanup operations.
*/ */
default void destroy() default void destroy()
{ {
@@ -63,4 +63,4 @@ public interface EventSender
{ {
return false; return false;
} }
} }

View File

@@ -25,15 +25,16 @@
*/ */
package org.alfresco.repo.event2; package org.alfresco.repo.event2;
import java.util.Optional;
import java.util.concurrent.Executor;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import org.alfresco.util.PropertyCheck;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.core.env.PropertyResolver; import org.springframework.core.env.PropertyResolver;
import java.util.Optional; import org.alfresco.util.PropertyCheck;
import java.util.concurrent.Executor;
public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender> public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
{ {
@@ -51,7 +52,7 @@ public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
private boolean legacySkipQueueConfig; private boolean legacySkipQueueConfig;
public EventSenderFactoryBean(@Autowired PropertyResolver propertyResolver, Event2MessageProducer event2MessageProducer, public EventSenderFactoryBean(@Autowired PropertyResolver propertyResolver, Event2MessageProducer event2MessageProducer,
Executor enqueueThreadPoolExecutor, Executor dequeueThreadPoolExecutor) Executor enqueueThreadPoolExecutor, Executor dequeueThreadPoolExecutor)
{ {
super(); super();
PropertyCheck.mandatory(this, "propertyResolver", propertyResolver); PropertyCheck.mandatory(this, "propertyResolver", propertyResolver);
@@ -155,4 +156,13 @@ public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
{ {
return event2MessageProducer; return event2MessageProducer;
} }
}
@Override
protected void destroyInstance(EventSender eventSender)
{
if (eventSender != null)
{
eventSender.destroy();
}
}
}

View File

@@ -37,6 +37,7 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.alfresco.transform.config.CoreFunction; import org.alfresco.transform.config.CoreFunction;
import org.alfresco.util.PropertyCheck; import org.alfresco.util.PropertyCheck;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
@@ -46,6 +47,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import static org.alfresco.model.ContentModel.PROP_CONTENT; import static org.alfresco.model.ContentModel.PROP_CONTENT;
import static org.alfresco.transform.common.RequestParamMap.DIRECT_ACCESS_URL; import static org.alfresco.transform.common.RequestParamMap.DIRECT_ACCESS_URL;
@@ -68,6 +70,7 @@ public class LocalTransformClient implements TransformClient, InitializingBean
private ContentService contentService; private ContentService contentService;
private RenditionService2Impl renditionService2; private RenditionService2Impl renditionService2;
private boolean directAccessUrlEnabled; private boolean directAccessUrlEnabled;
private int threadPoolSize;
private ExecutorService executorService; private ExecutorService executorService;
private ThreadLocal<LocalTransform> transform = new ThreadLocal<>(); private ThreadLocal<LocalTransform> transform = new ThreadLocal<>();
@@ -97,6 +100,11 @@ public class LocalTransformClient implements TransformClient, InitializingBean
this.directAccessUrlEnabled = directAccessUrlEnabled; this.directAccessUrlEnabled = directAccessUrlEnabled;
} }
public void setThreadPoolSize(int threadPoolSize)
{
this.threadPoolSize = threadPoolSize;
}
public void setExecutorService(ExecutorService executorService) public void setExecutorService(ExecutorService executorService)
{ {
this.executorService = executorService; this.executorService = executorService;
@@ -110,9 +118,11 @@ public class LocalTransformClient implements TransformClient, InitializingBean
PropertyCheck.mandatory(this, "contentService", contentService); PropertyCheck.mandatory(this, "contentService", contentService);
PropertyCheck.mandatory(this, "renditionService2", renditionService2); PropertyCheck.mandatory(this, "renditionService2", renditionService2);
PropertyCheck.mandatory(this, "directAccessUrlEnabled", directAccessUrlEnabled); PropertyCheck.mandatory(this, "directAccessUrlEnabled", directAccessUrlEnabled);
PropertyCheck.mandatory(this, "threadPoolSize", threadPoolSize);
if (executorService == null) if (executorService == null)
{ {
executorService = Executors.newCachedThreadPool(); ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("local-transform-%d").build();
executorService = Executors.newFixedThreadPool(threadPoolSize, threadFactory);
} }
} }

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,6 +25,24 @@
*/ */
package org.alfresco.repo.rendition2; 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.ContentModel;
import org.alfresco.model.RenditionModel; import org.alfresco.model.RenditionModel;
import org.alfresco.repo.content.ContentServicePolicies; import org.alfresco.repo.content.ContentServicePolicies;
@@ -51,23 +69,6 @@ import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.PropertyCheck; 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. * The Async Rendition service. Replaces the original deprecated RenditionService.
@@ -80,11 +81,19 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
public static final QName DEFAULT_RENDITION_CONTENT_PROP = ContentModel.PROP_CONTENT; public static final QName DEFAULT_RENDITION_CONTENT_PROP = ContentModel.PROP_CONTENT;
public static final String DEFAULT_MIMETYPE = MimetypeMap.MIMETYPE_TEXT_PLAIN; public static final String DEFAULT_MIMETYPE = MimetypeMap.MIMETYPE_TEXT_PLAIN;
public static final String MIMETYPE_METADATA_EXTRACT = "alfresco-metadata-extract";
public static final String MIMETYPE_METADATA_EMBED = "alfresco-metadata-embed";
public static final String DEFAULT_ENCODING = "UTF-8"; public static final String DEFAULT_ENCODING = "UTF-8";
public static final int SOURCE_HAS_NO_CONTENT = -1; public static final int SOURCE_HAS_NO_CONTENT = -1;
public static final int RENDITION2_DOES_NOT_EXIST = -2; public static final int RENDITION2_DOES_NOT_EXIST = -2;
// Allowed mimetypes to support text or metadata extract transforms when thumbnails are disabled.
private static final Set<String> ALLOWED_MIMETYPES = Set.of(
MimetypeMap.MIMETYPE_TEXT_PLAIN,
MIMETYPE_METADATA_EXTRACT,
MIMETYPE_METADATA_EMBED);
private static Log logger = LogFactory.getLog(RenditionService2Impl.class); private static Log logger = LogFactory.getLog(RenditionService2Impl.class);
// As Async transforms and renditions are so similar, this class provides a way to provide the code that is different. // As Async transforms and renditions are so similar, this class provides a way to provide the code that is different.
@@ -95,12 +104,10 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
abstract RenditionDefinition2 getRenditionDefinition(); abstract RenditionDefinition2 getRenditionDefinition();
void handleUnsupported(UnsupportedOperationException e) void handleUnsupported(UnsupportedOperationException e)
{ {}
}
void throwIllegalStateExceptionIfAlreadyDone(int sourceContentHashCode) void throwIllegalStateExceptionIfAlreadyDone(int sourceContentHashCode)
{ {}
}
} }
private TransactionService transactionService; private TransactionService transactionService;
@@ -217,8 +224,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
@Override @Override
public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition) public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition)
{ {
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() {
{
@Override @Override
public String getName() public String getName()
{ {
@@ -237,8 +243,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
@Override @Override
public void render(NodeRef sourceNodeRef, String renditionName) public void render(NodeRef sourceNodeRef, String renditionName)
{ {
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() {
{
@Override @Override
public String getName() public String getName()
{ {
@@ -261,7 +266,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
@Override @Override
public void handleUnsupported(UnsupportedOperationException e) 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); NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
if (renditionNode == null) if (renditionNode == null)
{ {
@@ -277,7 +282,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
int renditionContentHashCode = getRenditionContentHashCode(renditionNode); int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode+ " hashCodes"); logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode + " hashCodes");
} }
if (renditionContentHashCode == sourceContentHashCode) if (renditionContentHashCode == sourceContentHashCode)
{ {
@@ -291,7 +296,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
{ {
try try
{ {
if (!isEnabled()) if (!isAsyncAllowed(renderOrTransform))
{ {
throw new RenditionService2Exception("Async transforms and renditions are disabled " + throw new RenditionService2Exception("Async transforms and renditions are disabled " +
"(system.thumbnail.generate=false or renditionService2.enabled=false)."); "(system.thumbnail.generate=false or renditionService2.enabled=false).");
@@ -299,14 +304,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
if (!nodeService.exists(sourceNodeRef)) 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(); RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition();
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug(renderOrTransform.getName()+ ": transform " +sourceNodeRef); logger.debug(renderOrTransform.getName() + ": transform " + sourceNodeRef);
} }
AtomicBoolean supported = new AtomicBoolean(true); AtomicBoolean supported = new AtomicBoolean(true);
@@ -328,14 +333,13 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
String user = AuthenticationUtil.getRunAsUser(); String user = AuthenticationUtil.getRunAsUser();
RetryingTransactionHelper.RetryingTransactionCallback callback = () -> RetryingTransactionHelper.RetryingTransactionCallback callback = () -> {
{
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef); int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
if (!supported.get()) if (!supported.get())
{ {
if (logger.isDebugEnabled()) 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."); "The content might be too big or the source mimetype cannot be converted.");
} }
failure(sourceNodeRef, renditionDefinition, sourceContentHashCode); failure(sourceNodeRef, renditionDefinition, sourceContentHashCode);
@@ -372,26 +376,24 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
public void failure(NodeRef sourceNodeRef, RenditionDefinition2 renditionDefinition, int transformContentHashCode) public void failure(NodeRef sourceNodeRef, RenditionDefinition2 renditionDefinition, int transformContentHashCode)
{ {
// The original transaction may have already have failed // The original transaction may have already have failed
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode);
{ return null;
consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode); }, false, true));
return null;
}, false, true));
} }
public void consume(NodeRef sourceNodeRef, InputStream transformInputStream, RenditionDefinition2 renditionDefinition, public void consume(NodeRef sourceNodeRef, InputStream transformInputStream, RenditionDefinition2 renditionDefinition,
int transformContentHashCode) int transformContentHashCode)
{ {
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef); int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
if (logger.isDebugEnabled()) 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) if (renditionDefinition instanceof TransformDefinition)
{ {
TransformDefinition transformDefinition = (TransformDefinition)renditionDefinition; TransformDefinition transformDefinition = (TransformDefinition) renditionDefinition;
String targetMimetype = transformDefinition.getTargetMimetype(); String targetMimetype = transformDefinition.getTargetMimetype();
if (AsynchronousExtractor.isMetadataExtractMimetype(targetMimetype)) if (AsynchronousExtractor.isMetadataExtractMimetype(targetMimetype))
{ {
@@ -413,7 +415,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
private void consumeExtractedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream, private void consumeExtractedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream,
TransformDefinition transformDefinition, int transformContentHashCode) TransformDefinition transformDefinition, int transformContentHashCode)
{ {
if (transformInputStream == null) if (transformInputStream == null)
{ {
@@ -440,7 +442,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
private void consumeEmbeddedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream, private void consumeEmbeddedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream,
TransformDefinition transformDefinition, int transformContentHashCode) TransformDefinition transformDefinition, int transformContentHashCode)
{ {
if (transformInputStream == null) if (transformInputStream == null)
{ {
@@ -468,7 +470,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
private void consumeTransformReply(NodeRef sourceNodeRef, InputStream transformInputStream, private void consumeTransformReply(NodeRef sourceNodeRef, InputStream transformInputStream,
TransformDefinition transformDefinition, int transformContentHashCode) TransformDefinition transformDefinition, int transformContentHashCode)
{ {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
@@ -484,12 +486,10 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
/** /**
* Takes a transformation (InputStream) and attaches it as a rendition to the source node. * 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.
* 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, private void consumeRendition(NodeRef sourceNodeRef, int sourceContentHashCode, InputStream transformInputStream,
RenditionDefinition2 renditionDefinition, int transformContentHashCode) RenditionDefinition2 renditionDefinition, int transformContentHashCode)
{ {
String renditionName = renditionDefinition.getRenditionName(); String renditionName = renditionDefinition.getRenditionName();
if (transformContentHashCode != sourceContentHashCode) if (transformContentHashCode != sourceContentHashCode)
@@ -507,93 +507,92 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
(transformInputStream == null ? " to null as the transform failed" : " to the transform result")); (transformInputStream == null ? " to null as the transform failed" : " to the transform result"));
} }
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
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)
{ {
// 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 try
{ {
ruleService.disableRuleType(RuleType.UPDATE); // Set or replace rendition content
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE); ContentWriter contentWriter = contentService.getWriter(renditionNode, DEFAULT_RENDITION_CONTENT_PROP, true);
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE); String targetMimetype = renditionDefinition.getTargetMimetype();
contentWriter.setMimetype(targetMimetype);
contentWriter.setEncoding(DEFAULT_ENCODING);
ContentWriter renditionWriter = contentWriter;
renditionWriter.putContent(transformInputStream);
// If they do not exist create the rendition association and the rendition node. ContentReader contentReader = renditionWriter.getReader();
if (createRenditionNode) long sizeOfRendition = contentReader.getSize();
if (sizeOfRendition > 0L)
{ {
renditionNode = createRenditionNode(sourceNodeRef, renditionDefinition);
}
else if (!nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
{
nodeService.addAspect(renditionNode, RenditionModel.ASPECT_RENDITION2, null);
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug("Added rendition2 aspect to rendition " + renditionName + " on " + sourceNodeRef); logger.debug("Set rendition hashcode for " + renditionName);
}
}
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 else
{ {
logger.error("Transform was zero bytes for " + renditionName + " on " + sourceNodeRef);
clearRenditionContentData(renditionNode); clearRenditionContentData(renditionNode);
} }
if (!sourceHasAspectRenditioned)
{
nodeService.addAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED, null);
}
} }
catch (Exception e) catch (Exception e)
{ {
throw new RenditionService2Exception(TRANSFORMING_ERROR_MESSAGE + e.getMessage(), e); logger.error("Failed to copy transform InputStream into rendition " + renditionName + " on " + sourceNodeRef);
throw e;
} }
finally }
{ else
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE); {
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE); clearRenditionContentData(renditionNode);
ruleService.enableRuleType(RuleType.UPDATE); }
}
return null; if (!sourceHasAspectRenditioned)
}, false, true)); {
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));
} }
} }
@@ -634,14 +633,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
if (logger.isTraceEnabled()) 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)) if (nodeService.hasAspect(sourceNodeRef, ContentModel.ASPECT_THUMBNAIL_MODIFICATION))
{ {
List<String> thumbnailMods = (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA); List<String> thumbnailMods = (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA);
String target = null; String target = null;
for (String currThumbnailMod: thumbnailMods) for (String currThumbnailMod : thumbnailMods)
{ {
if (currThumbnailMod.startsWith(prefix)) if (currThumbnailMod.startsWith(prefix))
{ {
@@ -665,8 +664,7 @@ 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 * 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.
* sequences to which they were requested, this is used work out if a rendition should be replaced.
*/ */
private int getSourceContentHashCode(NodeRef sourceNodeRef) private int getSourceContentHashCode(NodeRef sourceNodeRef)
{ {
@@ -675,7 +673,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
if (contentData != null) if (contentData != null)
{ {
// Originally we used the contentData URL, but that is not enough if the mimetype changes. // 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) if (contentString != null)
{ {
hashCode = contentString.hashCode(); hashCode = contentString.hashCode();
@@ -685,13 +683,11 @@ 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. * 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.
* 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) 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; return RENDITION2_DOES_NOT_EXIST;
} }
@@ -699,7 +695,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
Serializable hashCode = nodeService.getProperty(renditionNode, PROP_RENDITION_CONTENT_HASH_CODE); Serializable hashCode = nodeService.getProperty(renditionNode, PROP_RENDITION_CONTENT_HASH_CODE);
return hashCode == null return hashCode == null
? SOURCE_HAS_NO_CONTENT ? SOURCE_HAS_NO_CONTENT
: (int)hashCode; : (int) hashCode;
} }
private NodeRef getRenditionNode(NodeRef sourceNodeRef, String renditionName) private NodeRef getRenditionNode(NodeRef sourceNodeRef, String renditionName)
@@ -773,11 +769,12 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
/** /**
* This method checks whether the specified source node is of a content class which has been registered for * This method checks whether the specified source node is of a content class which has been registered for rendition prevention.
* rendition prevention.
* *
* @param sourceNode the node to check. * @param sourceNode
* @throws RenditionService2PreventedException if the source node is configured for rendition prevention. * the node to check.
* @throws RenditionService2PreventedException
* if the source node is configured for rendition prevention.
*/ */
// This code is based on the old RenditionServiceImpl.checkSourceNodeForPreventionClass(...) // This code is based on the old RenditionServiceImpl.checkSourceNodeForPreventionClass(...)
private void checkSourceNodeForPreventionClass(NodeRef sourceNode) private void checkSourceNodeForPreventionClass(NodeRef sourceNode)
@@ -823,7 +820,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
for (ChildAssociationRef childAssoc : childAsocs) for (ChildAssociationRef childAssoc : childAsocs)
{ {
NodeRef renditionNode = childAssoc.getChildRef(); NodeRef renditionNode = childAssoc.getChildRef();
if (isRenditionAvailable(sourceNodeRef, renditionNode)) if (isRenditionAvailable(sourceNodeRef, renditionNode))
{ {
result.add(childAssoc); result.add(childAssoc);
@@ -833,8 +830,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
/** /**
* Indicates if the rendition is available. Failed renditions (there was an error) don't have a contentUrl * 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.
* and out of date renditions or those still being created don't have a matching contentHashCode.
*/ */
public boolean isRenditionAvailable(NodeRef sourceNodeRef, NodeRef renditionNode) public boolean isRenditionAvailable(NodeRef sourceNodeRef, NodeRef renditionNode)
{ {
@@ -852,7 +848,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
int renditionContentHashCode = getRenditionContentHashCode(renditionNode); int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
{ {
logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode+" hashcodes"); logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode + " hashcodes");
} }
if (sourceContentHashCode != renditionContentHashCode) if (sourceContentHashCode != renditionContentHashCode)
{ {
@@ -892,19 +888,17 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
ChildAssociationRef childAssoc = renditions.get(0); ChildAssociationRef childAssoc = renditions.get(0);
NodeRef renditionNode = childAssoc.getChildRef(); NodeRef renditionNode = childAssoc.getChildRef();
return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null: childAssoc; return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null : childAssoc;
} }
} }
@Override @Override
public void clearRenditionContentDataInTransaction(NodeRef renditionNode) public void clearRenditionContentDataInTransaction(NodeRef renditionNode)
{ {
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> clearRenditionContentData(renditionNode);
{ return null;
clearRenditionContentData(renditionNode); }, false, true));
return null;
}, false, true));
} }
@Override @Override
@@ -950,4 +944,23 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
} }
} }
// Checks if the given transform callback is a text extract transform for content indexing or metadata extract/embed.
private boolean isTextOrMetadataExtractTransform(RenderOrTransformCallBack renderOrTransform)
{
RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition();
return renditionDefinition != null && ALLOWED_MIMETYPES.contains(renditionDefinition.getTargetMimetype());
}
private boolean isAsyncAllowed(RenderOrTransformCallBack renderOrTransform)
{
// If enabled is false, all async transforms/renditions must be blocked
if (!enabled)
{
return false;
}
// If thumbnails are disabled, allow only text extract or metadata extract/embed transforms
return thumbnailsEnabled || isTextOrMetadataExtractTransform(renderOrTransform);
}
} }

View File

@@ -1,123 +1,121 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2023 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.security.authentication.identityservice; package org.alfresco.repo.security.authentication.identityservice;
import org.alfresco.repo.management.subsystems.ActivateableBean; import org.apache.commons.logging.Log;
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent; import org.apache.commons.logging.LogFactory;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant; import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException; import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
import org.apache.commons.logging.Log; import org.alfresco.repo.security.authentication.AuthenticationException;
import org.apache.commons.logging.LogFactory; 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. * 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>
* <br> * The {@link IdentityServiceAuthenticationComponent#identityServiceFacade} can be null in which case this authenticator will just fall through to the next one in the chain.
* 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
*/ {
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 final Log LOGGER = LogFactory.getLog(IdentityServiceAuthenticationComponent.class); private IdentityServiceFacade identityServiceFacade;
/** client used to authenticate user credentials against Authorization Server **/ /** enabled flag for the identity service subsystem **/
private IdentityServiceFacade identityServiceFacade; private boolean active;
/** enabled flag for the identity service subsystem**/
private boolean active; private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
private IdentityServiceJITProvisioningHandler jitProvisioningHandler; private boolean allowGuestLogin;
private boolean allowGuestLogin; public void setIdentityServiceFacade(IdentityServiceFacade identityServiceFacade)
{
public void setIdentityServiceFacade(IdentityServiceFacade identityServiceFacade) this.identityServiceFacade = identityServiceFacade;
{ }
this.identityServiceFacade = identityServiceFacade;
} public void setAllowGuestLogin(boolean allowGuestLogin)
{
public void setAllowGuestLogin(boolean allowGuestLogin) this.allowGuestLogin = allowGuestLogin;
{ }
this.allowGuestLogin = allowGuestLogin;
} public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
{
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler) this.jitProvisioningHandler = jitProvisioningHandler;
{ }
this.jitProvisioningHandler = jitProvisioningHandler;
} @Override
public void authenticateImpl(String userName, char[] password) throws AuthenticationException
@Override {
public void authenticateImpl(String userName, char[] password) throws AuthenticationException if (identityServiceFacade == null)
{ {
if (identityServiceFacade == null) if (LOGGER.isDebugEnabled())
{ {
if (LOGGER.isDebugEnabled()) LOGGER.debug("IdentityServiceFacade was not set, possibly due to the 'identity-service.authentication.enable-username-password-authentication=false' property.");
{ }
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.");
}
throw new AuthenticationException("User not authenticated because IdentityServiceFacade was not set.");
} try
{
try // Attempt to verify user credentials
{ IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(AuthorizationGrant.password(userName, String.valueOf(password)));
// 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)
String normalizedUsername = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(accessTokenAuthorization.getAccessToken().getTokenValue()) .orElseThrow(() -> new AuthenticationException("Failed to extract username from token and user info endpoint."));
.map(OIDCUserInfo::username) // Verification was successful so treat as authenticated user
.orElseThrow(() -> new AuthenticationException("Failed to extract username from token and user info endpoint.")); setCurrentUser(normalizedUsername);
// Verification was successful so treat as authenticated user }
setCurrentUser(normalizedUsername); catch (IdentityServiceFacadeException e)
} {
catch (IdentityServiceFacadeException e) throw new AuthenticationException("Failed to verify user credentials against the OAuth2 Authorization Server.", e);
{ }
throw new AuthenticationException("Failed to verify user credentials against the OAuth2 Authorization Server.", e); catch (RuntimeException e)
} {
catch (RuntimeException e) throw new AuthenticationException("Failed to verify user credentials.", e);
{ }
throw new AuthenticationException("Failed to verify user credentials.", e); }
}
} public void setActive(boolean active)
{
public void setActive(boolean active) this.active = active;
{ }
this.active = active;
} @Override
public boolean isActive()
@Override {
public boolean isActive() return active;
{ }
return active;
} @Override
protected boolean implementationAllowsGuestLogin()
@Override {
protected boolean implementationAllowsGuestLogin() return allowGuestLogin;
{ }
return allowGuestLogin; }
}
}

View File

@@ -1,330 +1,413 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.security.authentication.identityservice; package org.alfresco.repo.security.authentication.identityservice;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
/** /**
* Class to hold configuration for the Identity Service. * Class to hold configuration for the Identity Service.
* *
* @author Gavin Cornwell * @author Gavin Cornwell
*/ */
@SuppressWarnings("PMD.ExcessivePublicCount") @SuppressWarnings("PMD.ExcessivePublicCount")
public class IdentityServiceConfig public class IdentityServiceConfig
{ {
private static final String REALMS = "realms"; private static final String REALMS = "realms";
private int clientConnectionTimeout; private int clientConnectionTimeout;
private int clientSocketTimeout; private int clientSocketTimeout;
private String issuerUrl; private String issuerUrl;
private String audience; private String audience;
// client id // client id
private String resource; private String resource;
private String clientSecret; private String clientSecret;
private String authServerUrl; private String authServerUrl;
private String realm; private String realm;
private int connectionPoolSize; private int connectionPoolSize;
private boolean allowAnyHostname; private boolean allowAnyHostname;
private boolean disableTrustManager; private boolean disableTrustManager;
private String truststore; private String truststore;
private String truststorePassword; private String truststorePassword;
private String clientKeystore; private String clientKeystore;
private String clientKeystorePassword; private String clientKeystorePassword;
private String clientKeyPassword; private String clientKeyPassword;
private String realmKey; private String realmKey;
private int publicKeyCacheTtl; private int publicKeyCacheTtl;
private boolean publicClient; private boolean publicClient;
private String principalAttribute; private String principalAttribute;
private boolean clientIdValidationDisabled; private boolean clientIdValidationDisabled;
private String adminConsoleRedirectPath; private String adminConsoleRedirectPath;
private String signatureAlgorithms; private String signatureAlgorithms;
private String adminConsoleScopes;
/** private String passwordGrantScopes;
* private String issuerAttribute;
* @return Client connection timeout in milliseconds. private String firstNameAttribute;
*/ private String lastNameAttribute;
public int getClientConnectionTimeout() private String emailAttribute;
{ private long jwtClockSkewMs;
return clientConnectionTimeout;
} /**
*
/** * @return Client connection timeout in milliseconds.
* */
* @param clientConnectionTimeout Client connection timeout in milliseconds. public int getClientConnectionTimeout()
*/ {
public void setClientConnectionTimeout(int clientConnectionTimeout) return clientConnectionTimeout;
{ }
this.clientConnectionTimeout = clientConnectionTimeout;
} /**
*
/** * @param clientConnectionTimeout
* * Client connection timeout in milliseconds.
* @return Client socket timeout in milliseconds.s */
*/ public void setClientConnectionTimeout(int clientConnectionTimeout)
public int getClientSocketTimeout() {
{ this.clientConnectionTimeout = clientConnectionTimeout;
return clientSocketTimeout; }
}
/**
/** *
* * @return Client socket timeout in milliseconds.s
* @param clientSocketTimeout Client socket timeout in milliseconds. */
*/ public int getClientSocketTimeout()
public void setClientSocketTimeout(int clientSocketTimeout) {
{ return clientSocketTimeout;
this.clientSocketTimeout = clientSocketTimeout; }
}
/**
public void setConnectionPoolSize(int connectionPoolSize) *
{ * @param clientSocketTimeout
this.connectionPoolSize = connectionPoolSize; * Client socket timeout in milliseconds.
} */
public void setClientSocketTimeout(int clientSocketTimeout)
public int getConnectionPoolSize() {
{ this.clientSocketTimeout = clientSocketTimeout;
return connectionPoolSize; }
}
public void setConnectionPoolSize(int connectionPoolSize)
public String getIssuerUrl() {
{ this.connectionPoolSize = connectionPoolSize;
return issuerUrl; }
}
public int getConnectionPoolSize()
public void setIssuerUrl(String issuerUrl) {
{ return connectionPoolSize;
this.issuerUrl = issuerUrl; }
}
public String getIssuerUrl()
public String getAudience() {
{ return issuerUrl;
return audience; }
}
public void setIssuerUrl(String issuerUrl)
public void setAudience(String audience) {
{ this.issuerUrl = issuerUrl;
this.audience = audience; }
}
public String getAudience()
public String getAuthServerUrl() {
{ return audience;
return Optional.ofNullable(realm) }
.filter(StringUtils::isNotBlank)
.filter(realm -> StringUtils.isNotBlank(authServerUrl)) public void setAudience(String audience)
.map(realm -> UriComponentsBuilder.fromUriString(authServerUrl) {
.pathSegment(REALMS, realm) this.audience = audience;
.build() }
.toString())
.orElse(authServerUrl); public String getAuthServerUrl()
} {
return Optional.ofNullable(realm)
public void setAuthServerUrl(String authServerUrl) .filter(StringUtils::isNotBlank)
{ .filter(realm -> StringUtils.isNotBlank(authServerUrl))
this.authServerUrl = authServerUrl; .map(realm -> UriComponentsBuilder.fromUriString(authServerUrl)
} .pathSegment(REALMS, realm)
.build()
public String getRealm() .toString())
{ .orElse(authServerUrl);
return realm; }
}
public void setAuthServerUrl(String authServerUrl)
public void setRealm(String realm) {
{ this.authServerUrl = authServerUrl;
this.realm = realm; }
}
public String getRealm()
public String getResource() {
{ return realm;
return resource; }
}
public void setRealm(String realm)
public void setResource(String resource) {
{ this.realm = realm;
this.resource = resource; }
}
public String getResource()
public void setClientSecret(String clientSecret) {
{ return resource;
this.clientSecret = clientSecret; }
}
public void setResource(String resource)
public String getClientSecret() {
{ this.resource = resource;
return Optional.ofNullable(clientSecret) }
.orElse("");
} public void setClientSecret(String clientSecret)
{
public void setAllowAnyHostname(boolean allowAnyHostname) this.clientSecret = clientSecret;
{ }
this.allowAnyHostname = allowAnyHostname;
} public String getClientSecret()
{
public boolean isAllowAnyHostname() return Optional.ofNullable(clientSecret)
{ .orElse("");
return allowAnyHostname; }
}
public void setAllowAnyHostname(boolean allowAnyHostname)
public void setDisableTrustManager(boolean disableTrustManager) {
{ this.allowAnyHostname = allowAnyHostname;
this.disableTrustManager = disableTrustManager; }
}
public boolean isAllowAnyHostname()
public boolean isDisableTrustManager() {
{ return allowAnyHostname;
return disableTrustManager; }
}
public void setDisableTrustManager(boolean disableTrustManager)
public void setTruststore(String truststore) {
{ this.disableTrustManager = disableTrustManager;
this.truststore = truststore; }
}
public boolean isDisableTrustManager()
public String getTruststore() {
{ return disableTrustManager;
return truststore; }
}
public void setTruststore(String truststore)
public void setTruststorePassword(String truststorePassword) {
{ this.truststore = truststore;
this.truststorePassword = truststorePassword; }
}
public String getTruststore()
public String getTruststorePassword() {
{ return truststore;
return truststorePassword; }
}
public void setTruststorePassword(String truststorePassword)
public void setClientKeystore(String clientKeystore) {
{ this.truststorePassword = truststorePassword;
this.clientKeystore = clientKeystore; }
}
public String getTruststorePassword()
public String getClientKeystore() {
{ return truststorePassword;
return clientKeystore; }
}
public void setClientKeystore(String clientKeystore)
public void setClientKeystorePassword(String clientKeystorePassword) {
{ this.clientKeystore = clientKeystore;
this.clientKeystorePassword = clientKeystorePassword; }
}
public String getClientKeystore()
public String getClientKeystorePassword() {
{ return clientKeystore;
return clientKeystorePassword; }
}
public void setClientKeystorePassword(String clientKeystorePassword)
public void setClientKeyPassword(String clientKeyPassword) {
{ this.clientKeystorePassword = clientKeystorePassword;
this.clientKeyPassword = clientKeyPassword; }
}
public String getClientKeystorePassword()
public String getClientKeyPassword() {
{ return clientKeystorePassword;
return clientKeyPassword; }
}
public void setClientKeyPassword(String clientKeyPassword)
public void setRealmKey(String realmKey) {
{ this.clientKeyPassword = clientKeyPassword;
this.realmKey = realmKey; }
}
public String getClientKeyPassword()
public String getRealmKey() {
{ return clientKeyPassword;
return realmKey; }
}
public void setRealmKey(String realmKey)
public void setPublicKeyCacheTtl(int publicKeyCacheTtl) {
{ this.realmKey = realmKey;
this.publicKeyCacheTtl = publicKeyCacheTtl; }
}
public String getRealmKey()
public int getPublicKeyCacheTtl() {
{ return realmKey;
return publicKeyCacheTtl; }
}
public void setPublicKeyCacheTtl(int publicKeyCacheTtl)
public void setPublicClient(boolean publicClient) {
{ this.publicKeyCacheTtl = publicKeyCacheTtl;
this.publicClient = publicClient; }
}
public int getPublicKeyCacheTtl()
public boolean isPublicClient() {
{ return publicKeyCacheTtl;
return publicClient; }
}
public void setPublicClient(boolean publicClient)
public String getPrincipalAttribute() {
{ this.publicClient = publicClient;
return principalAttribute; }
}
public boolean isPublicClient()
public void setPrincipalAttribute(String principalAttribute) {
{ return publicClient;
this.principalAttribute = principalAttribute; }
}
public String getPrincipalAttribute()
public boolean isClientIdValidationDisabled() {
{ return principalAttribute;
return clientIdValidationDisabled; }
}
public void setPrincipalAttribute(String principalAttribute)
public void setClientIdValidationDisabled(boolean clientIdValidationDisabled) {
{ this.principalAttribute = principalAttribute;
this.clientIdValidationDisabled = clientIdValidationDisabled; }
}
public boolean isClientIdValidationDisabled()
public String getAdminConsoleRedirectPath() {
{ return clientIdValidationDisabled;
return adminConsoleRedirectPath; }
}
public void setClientIdValidationDisabled(boolean clientIdValidationDisabled)
public void setAdminConsoleRedirectPath(String adminConsoleRedirectPath) {
{ this.clientIdValidationDisabled = clientIdValidationDisabled;
this.adminConsoleRedirectPath = adminConsoleRedirectPath; }
}
public String getAdminConsoleRedirectPath()
public Set<SignatureAlgorithm> getSignatureAlgorithms() {
{ return adminConsoleRedirectPath;
return Stream.of(signatureAlgorithms.split(",")) }
.map(String::trim)
.map(SignatureAlgorithm::from) public void setAdminConsoleRedirectPath(String adminConsoleRedirectPath)
.filter(Objects::nonNull) {
.collect(Collectors.toUnmodifiableSet()); this.adminConsoleRedirectPath = adminConsoleRedirectPath;
} }
public void setSignatureAlgorithms(String signatureAlgorithms) public Set<SignatureAlgorithm> getSignatureAlgorithms()
{ {
this.signatureAlgorithms = signatureAlgorithms; 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> 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;
}
}

View File

@@ -1,249 +1,265 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.security.authentication.identityservice; package org.alfresco.repo.security.authentication.identityservice;
import static java.util.Objects.nonNull; import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import java.time.Instant; import java.time.Instant;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
/** import org.alfresco.repo.security.authentication.identityservice.user.DecodedTokenUser;
* Allows to interact with the Identity Service import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
*/
public interface IdentityServiceFacade /**
{ * Allows to interact with the Identity Service
/** */
* Returns {@link AccessToken} based authorization for provided {@link AuthorizationGrant}. public interface IdentityServiceFacade
* @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. * Returns {@link AccessToken} based authorization for provided {@link AuthorizationGrant}.
*/ *
AccessTokenAuthorization authorize(AuthorizationGrant grant) throws AuthorizationException; * @param grant
* the OAuth2 grant provided by the Resource Owner.
/** * @return {@link AccessTokenAuthorization} containing access token and optional refresh token.
* Decodes the access token into the {@link DecodedAccessToken} which contains claims connected with a given token. * @throws {@link
* @param token {@link String} with encoded access token value. * AuthorizationException} when provided grant cannot be exchanged for the access token.
* @return {@link DecodedAccessToken} containing decoded claims. */
* @throws {@link TokenDecodingException} when token decoding failed. AccessTokenAuthorization authorize(AuthorizationGrant grant) throws AuthorizationException;
*/
DecodedAccessToken decodeToken(String token) throws TokenDecodingException; /**
* Decodes the access token into the {@link DecodedAccessToken} which contains claims connected with a given token.
/** *
* Gets claims about the authenticated user, * @param token
* such as name and email address, via the UserInfo endpoint of the OpenID provider. * {@link String} with encoded access token value.
* @param token {@link String} with encoded access token value. * @return {@link DecodedAccessToken} containing decoded claims.
* @param principalAttribute {@link String} the attribute name used to access the user's name from the user info response. * @throws {@link
* @return {@link OIDCUserInfo} containing user claims. * TokenDecodingException} when token decoding failed.
*/ */
Optional<OIDCUserInfo> getUserInfo(String token, String principalAttribute); DecodedAccessToken decodeToken(String token) throws TokenDecodingException;
/** /**
* Gets a client registration * Gets claims about the authenticated user, such as name and email address, via the UserInfo endpoint of the OpenID provider.
*/ *
ClientRegistration getClientRegistration(); * @param token
* {@link String} with encoded access token value.
class IdentityServiceFacadeException extends RuntimeException * @param userInfoAttrMapping
{ * {@link UserInfoAttrMapping} containing the mapping of claims.
public IdentityServiceFacadeException(String message) * @return {@link DecodedTokenUser} containing user claims or {@link Optional#empty()} if the token does not contain a username claim.
{ */
super(message); Optional<DecodedTokenUser> getUserInfo(String token, UserInfoAttrMapping userInfoAttrMapping);
}
/**
IdentityServiceFacadeException(String message, Throwable cause) * Gets a client registration
{ */
super(message, cause); ClientRegistration getClientRegistration();
}
} class IdentityServiceFacadeException extends RuntimeException
{
class AuthorizationException extends IdentityServiceFacadeException public IdentityServiceFacadeException(String message)
{ {
AuthorizationException(String message) super(message);
{ }
super(message);
} IdentityServiceFacadeException(String message, Throwable cause)
{
AuthorizationException(String message, Throwable cause) super(message, cause);
{ }
super(message, cause); }
}
} class AuthorizationException extends IdentityServiceFacadeException
{
class UserInfoException extends IdentityServiceFacadeException AuthorizationException(String message)
{ {
super(message);
UserInfoException(String message) }
{
super(message); AuthorizationException(String message, Throwable cause)
} {
super(message, cause);
UserInfoException(String message, Throwable cause) }
{ }
super(message, cause);
} class UserInfoException extends IdentityServiceFacadeException
} {
class TokenDecodingException extends IdentityServiceFacadeException UserInfoException(String message)
{ {
TokenDecodingException(String message) super(message);
{ }
super(message);
} UserInfoException(String message, Throwable cause)
{
TokenDecodingException(String message, Throwable cause) super(message, cause);
{ }
super(message, cause); }
}
} class TokenDecodingException extends IdentityServiceFacadeException
{
/** TokenDecodingException(String message)
* Represents access token authorization with optional refresh token. {
*/ super(message);
interface AccessTokenAuthorization }
{
/** TokenDecodingException(String message, Throwable cause)
* Required {@link AccessToken} {
* @return {@link AccessToken} super(message, cause);
*/ }
AccessToken getAccessToken(); }
/** /**
* Optional refresh token. * Represents access token authorization with optional refresh token.
* @return Refresh token or {@code null} */
*/ interface AccessTokenAuthorization
String getRefreshTokenValue(); {
} /**
* Required {@link AccessToken}
interface AccessToken { *
String getTokenValue(); * @return {@link AccessToken}
Instant getExpiresAt(); */
} AccessToken getAccessToken();
interface DecodedAccessToken extends AccessToken /**
{ * Optional refresh token.
Object getClaim(String claim); *
} * @return Refresh token or {@code null}
*/
class AuthorizationGrant { String getRefreshTokenValue();
private final String username; }
private final String password;
private final String refreshToken; interface AccessToken
private final String authorizationCode; {
private final String redirectUri; String getTokenValue();
private AuthorizationGrant(String username, String password, String refreshToken, String authorizationCode, String redirectUri) Instant getExpiresAt();
{ }
this.username = username;
this.password = password; interface DecodedAccessToken extends AccessToken
this.refreshToken = refreshToken; {
this.authorizationCode = authorizationCode; Object getClaim(String claim);
this.redirectUri = redirectUri; }
}
class AuthorizationGrant
public static AuthorizationGrant password(String username, String password) {
{ private final String username;
return new AuthorizationGrant(requireNonNull(username), requireNonNull(password), null, null, null); private final String password;
} private final String refreshToken;
private final String authorizationCode;
public static AuthorizationGrant refreshToken(String refreshToken) private final String redirectUri;
{
return new AuthorizationGrant(null, null, requireNonNull(refreshToken), null, null); private AuthorizationGrant(String username, String password, String refreshToken, String authorizationCode, String redirectUri)
} {
this.username = username;
public static AuthorizationGrant authorizationCode(String authorizationCode, String redirectUri) this.password = password;
{ this.refreshToken = refreshToken;
return new AuthorizationGrant(null, null, null, requireNonNull(authorizationCode), requireNonNull(redirectUri)); this.authorizationCode = authorizationCode;
} this.redirectUri = redirectUri;
}
boolean isPassword()
{ public static AuthorizationGrant password(String username, String password)
return nonNull(username); {
} return new AuthorizationGrant(requireNonNull(username), requireNonNull(password), null, null, null);
}
boolean isRefreshToken()
{ public static AuthorizationGrant refreshToken(String refreshToken)
return nonNull(refreshToken); {
} return new AuthorizationGrant(null, null, requireNonNull(refreshToken), null, null);
}
boolean isAuthorizationCode()
{ public static AuthorizationGrant authorizationCode(String authorizationCode, String redirectUri)
return nonNull(authorizationCode); {
} return new AuthorizationGrant(null, null, null, requireNonNull(authorizationCode), requireNonNull(redirectUri));
}
String getUsername()
{ boolean isPassword()
return username; {
} return nonNull(username);
}
String getPassword()
{ boolean isRefreshToken()
return password; {
} return nonNull(refreshToken);
}
String getRefreshToken()
{ boolean isAuthorizationCode()
return refreshToken; {
} return nonNull(authorizationCode);
}
String getAuthorizationCode()
{ String getUsername()
return authorizationCode; {
} return username;
}
String getRedirectUri()
{ String getPassword()
return redirectUri; {
} return password;
}
@Override
public boolean equals(Object o) String getRefreshToken()
{ {
if (this == o) return refreshToken;
{ }
return true;
} String getAuthorizationCode()
if (o == null || getClass() != o.getClass()) {
{ return authorizationCode;
return false; }
}
AuthorizationGrant that = (AuthorizationGrant) o; String getRedirectUri()
return Objects.equals(username, that.username) && {
Objects.equals(password, that.password) && return redirectUri;
Objects.equals(refreshToken, that.refreshToken) && }
Objects.equals(authorizationCode, that.authorizationCode) &&
Objects.equals(redirectUri, that.redirectUri); @Override
} public boolean equals(Object o)
{
@Override if (this == o)
public int hashCode() {
{ return true;
return Objects.hash(username, password, refreshToken, authorizationCode, redirectUri); }
} 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);
}
}
}

View File

@@ -30,59 +30,38 @@ import java.io.Serializable;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Predicate; import java.util.function.Predicate;
import com.nimbusds.openid.connect.sdk.claims.PersonClaims; import org.apache.commons.lang3.StringUtils;
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.DecodedAccessToken; 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.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.lang3.StringUtils;
/** /**
* This class handles Just in Time user provisioning. It extracts {@link OIDCUserInfo} * 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.
* from {@link IdentityServiceFacade.DecodedAccessToken} or {@link UserInfo}
* and creates a new user if it does not exist in the repository.
*/ */
public class IdentityServiceJITProvisioningHandler public class IdentityServiceJITProvisioningHandler
{ {
private final IdentityServiceConfig identityServiceConfig;
private final IdentityServiceFacade identityServiceFacade; private final IdentityServiceFacade identityServiceFacade;
private final PersonService personService; private final PersonService personService;
private final TransactionService transactionService; private final TransactionService transactionService;
private final IdentityServiceConfig identityServiceConfig;
private final BiFunction<DecodedAccessToken, String, Optional<? extends OIDCUserInfo>> mapTokenToUserInfoResponse = (token, usernameMappingClaim) -> { private UserInfoAttrMapping userInfoAttrMapping;
Optional<String> firstName = Optional.ofNullable(token) private TokenUserToOIDCUserMapper tokenUserToOIDCUserMapper;
.map(jwtToken -> jwtToken.getClaim(PersonClaims.GIVEN_NAME_CLAIM_NAME)) private AccessTokenToDecodedTokenUserMapper tokenToDecodedTokenUserMapper;
.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, public IdentityServiceJITProvisioningHandler(IdentityServiceFacade identityServiceFacade,
PersonService personService, PersonService personService,
TransactionService transactionService, TransactionService transactionService,
IdentityServiceConfig identityServiceConfig) IdentityServiceConfig identityServiceConfig)
{ {
this.identityServiceFacade = identityServiceFacade; this.identityServiceFacade = identityServiceFacade;
this.personService = personService; this.personService = personService;
@@ -90,94 +69,95 @@ public class IdentityServiceJITProvisioningHandler
this.identityServiceConfig = identityServiceConfig; 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) public Optional<OIDCUserInfo> extractUserInfoAndCreateUserIfNeeded(String bearerToken)
{ {
Optional<OIDCUserInfo> userInfoResponse = Optional.ofNullable(bearerToken) if (userInfoAttrMapping == null)
.filter(Predicate.not(String::isEmpty))
.flatMap(token -> extractUserInfoResponseFromAccessToken(token)
.filter(userInfo -> StringUtils.isNotEmpty(userInfo.username()))
.or(() -> extractUserInfoResponseFromEndpoint(token)));
if (transactionService.isReadOnly() || userInfoResponse.isEmpty())
{ {
return userInfoResponse; initMappers(identityServiceConfig);
} }
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Optional<OIDCUserInfo>>()
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())
{ {
return oidcUserInfo;
}
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<>() {
@Override @Override
public Optional<OIDCUserInfo> doWork() throws Exception public Optional<OIDCUserInfo> doWork() throws Exception
{ {
return userInfoResponse.map(userInfo -> { return oidcUserInfo.map(oidcUser -> {
if (userInfo.username() != null && personService.createMissingPeople() if (userDoesNotExistsAndCanBeCreated(oidcUser))
&& !personService.personExists(userInfo.username()))
{ {
if (!userInfo.allFieldsNotEmpty()) if (!oidcUser.allFieldsNotEmpty())
{ {
userInfo = extractUserInfoResponseFromEndpoint(bearerToken).orElse(userInfo); oidcUser = extractUserInfoResponseFromEndpoint(bearerToken, userInfoAttrMapping)
.map(tokenUserToOIDCUserMapper::toOIDCUser)
.orElse(oidcUser);
} }
Map<QName, Serializable> properties = new HashMap<>(); createPerson(oidcUser);
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 userInfo; return oidcUser;
}); });
} }
}, AuthenticationUtil.getSystemUserName()); }, AuthenticationUtil.getSystemUserName());
} }
private Optional<OIDCUserInfo> extractUserInfoResponseFromAccessToken(String bearerToken) 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)
{ {
return Optional.ofNullable(bearerToken) return Optional.ofNullable(bearerToken)
.map(identityServiceFacade::decodeToken) .map(identityServiceFacade::decodeToken)
.flatMap(decodedToken -> mapTokenToUserInfoResponse.apply(decodedToken, .flatMap(tokenToDecodedTokenUserMapper::toDecodedTokenUser);
identityServiceConfig.getPrincipalAttribute()));
} }
private Optional<OIDCUserInfo> extractUserInfoResponseFromEndpoint(String bearerToken) private Optional<DecodedTokenUser> extractUserInfoResponseFromEndpoint(String bearerToken, UserInfoAttrMapping userInfoAttrMapping)
{ {
return identityServiceFacade.getUserInfo(bearerToken, return identityServiceFacade.getUserInfo(bearerToken, userInfoAttrMapping)
StringUtils.isNotBlank(identityServiceConfig.getPrincipalAttribute()) ? .filter(userInfo -> userInfo.username() != null && !userInfo.username().isEmpty());
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)
{ {
if (userId == null) Map<QName, Serializable> properties = new HashMap<>();
{ properties.put(ContentModel.PROP_USERNAME, userInfo.username());
return null; 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
String normalized = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<String>() personService.createPerson(properties);
{
@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());
}
}

View File

@@ -1,181 +1,181 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2023 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.security.authentication.identityservice; package org.alfresco.repo.security.authentication.identityservice;
import jakarta.servlet.http.HttpServletRequest; import java.util.Optional;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.apache.commons.logging.Log;
import org.alfresco.repo.management.subsystems.ActivateableBean; import org.apache.commons.logging.LogFactory;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException; import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.apache.commons.logging.Log; import org.alfresco.repo.security.authentication.AuthenticationException;
import org.apache.commons.logging.LogFactory; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; 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. * A {@link RemoteUserMapper} implementation that detects and validates JWTs issued by the Alfresco Identity Service.
* *
* @author Gavin Cornwell * @author Gavin Cornwell
*/ */
public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, ActivateableBean public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, ActivateableBean
{ {
private static final Log LOGGER = LogFactory.getLog(IdentityServiceRemoteUserMapper.class); private static final Log LOGGER = LogFactory.getLog(IdentityServiceRemoteUserMapper.class);
/** Is the mapper enabled */ /** Is the mapper enabled */
private boolean isEnabled; private boolean isEnabled;
/** Are token validation failures handled silently? */ /** Are token validation failures handled silently? */
private boolean isValidationFailureSilent; private boolean isValidationFailureSilent;
private BearerTokenResolver bearerTokenResolver; private BearerTokenResolver bearerTokenResolver;
private IdentityServiceJITProvisioningHandler jitProvisioningHandler; private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
/** /**
* Sets the active flag * Sets the active flag
* *
* @param isEnabled true to enable the subsystem * @param isEnabled
*/ * true to enable the subsystem
public void setActive(boolean isEnabled) */
{ public void setActive(boolean isEnabled)
this.isEnabled = isEnabled; {
} this.isEnabled = isEnabled;
}
/**
* Determines whether token validation failures are silent /**
* * Determines whether token validation failures are silent
* @param silent true to silently fail, false to throw an exception *
*/ * @param silent
public void setValidationFailureSilent(boolean silent) * true to silently fail, false to throw an exception
{ */
this.isValidationFailureSilent = silent; public void setValidationFailureSilent(boolean silent)
} {
this.isValidationFailureSilent = silent;
public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) }
{
this.bearerTokenResolver = bearerTokenResolver; public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver)
} {
this.bearerTokenResolver = bearerTokenResolver;
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler) }
{
this.jitProvisioningHandler = jitProvisioningHandler; public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
} {
this.jitProvisioningHandler = jitProvisioningHandler;
/* }
* (non-Javadoc)
* @see org.alfresco.web.app.servlet.RemoteUserMapper#getRemoteUser(jakarta.servlet.http.HttpServletRequest) /* (non-Javadoc)
*/ *
@Override * @see org.alfresco.web.app.servlet.RemoteUserMapper#getRemoteUser(jakarta.servlet.http.HttpServletRequest) */
public String getRemoteUser(HttpServletRequest request) @Override
{ public String getRemoteUser(HttpServletRequest request)
LOGGER.trace("Retrieving username from http request..."); {
LOGGER.trace("Retrieving username from http request...");
if (!this.isEnabled)
{ if (!this.isEnabled)
LOGGER.debug("IdentityServiceRemoteUserMapper is disabled, returning null."); {
return null; LOGGER.debug("IdentityServiceRemoteUserMapper is disabled, returning null.");
} return null;
try }
{ try
String normalizedUserId = extractUserFromHeader(request); {
String normalizedUserId = extractUserFromHeader(request);
if (normalizedUserId != null) if (normalizedUserId != null)
{ {
// Normalize the user ID taking into account case sensitivity settings // Normalize the user ID taking into account case sensitivity settings
LOGGER.trace("Returning userId: " + AuthenticationUtil.maskUsername(normalizedUserId)); LOGGER.trace("Returning userId: " + AuthenticationUtil.maskUsername(normalizedUserId));
return normalizedUserId; return normalizedUserId;
} }
} }
catch (IdentityServiceFacadeException e) catch (IdentityServiceFacadeException e)
{ {
if (!isValidationFailureSilent) if (!isValidationFailureSilent)
{ {
throw new AuthenticationException("Failed to extract username from token: " + e.getMessage(), e); throw new AuthenticationException("Failed to extract username from token: " + e.getMessage(), e);
} }
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e); LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
} }
catch (RuntimeException e) catch (RuntimeException e)
{ {
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e); LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
} }
LOGGER.trace("Could not identify a userId. Returning null."); LOGGER.trace("Could not identify a userId. Returning null.");
return null; return null;
} }
/* /* (non-Javadoc)
* (non-Javadoc) *
* @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive() * @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive() */
*/ public boolean isActive()
public boolean isActive() {
{ return this.isEnabled;
return this.isEnabled; }
}
/**
/** * Extracts the user name from the JWT in the given request.
* Extracts the user name from the JWT in the given request. *
* * @param request
* @param request The request containing the JWT * The request containing the JWT
* @return The username or null if it can not be determined * @return The username or null if it can not be determined
*/ */
private String extractUserFromHeader(HttpServletRequest request) private String extractUserFromHeader(HttpServletRequest request)
{ {
// try authenticating with bearer token first // try authenticating with bearer token first
LOGGER.debug("Trying bearer token..."); LOGGER.debug("Trying bearer token...");
final String bearerToken; final String bearerToken;
try try
{ {
bearerToken = bearerTokenResolver.resolve(request); bearerToken = bearerTokenResolver.resolve(request);
} }
catch (OAuth2AuthenticationException e) catch (OAuth2AuthenticationException e)
{ {
LOGGER.debug("Failed to resolve Bearer token.", e); LOGGER.debug("Failed to resolve Bearer token.", e);
return null; return null;
} }
final Optional<String> possibleUsername = jitProvisioningHandler final Optional<String> possibleUsername = jitProvisioningHandler
.extractUserInfoAndCreateUserIfNeeded(bearerToken) .extractUserInfoAndCreateUserIfNeeded(bearerToken)
.map(OIDCUserInfo::username); .map(OIDCUserInfo::username);
if (possibleUsername.isEmpty()) if (possibleUsername.isEmpty())
{ {
LOGGER.debug("User could not be authenticated by IdentityServiceRemoteUserMapper."); LOGGER.debug("User could not be authenticated by IdentityServiceRemoteUserMapper.");
return null; return null;
} }
String normalizedUsername = possibleUsername.get(); String normalizedUsername = possibleUsername.get();
LOGGER.trace("Extracted username: " + AuthenticationUtil.maskUsername(normalizedUsername)); LOGGER.trace("Extracted username: " + AuthenticationUtil.maskUsername(normalizedUsername));
return normalizedUsername; return normalizedUsername;
} }
} }

View File

@@ -30,21 +30,12 @@ import static java.util.Objects.requireNonNull;
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceMetadataKey.AUDIENCE; 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.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate;
import com.nimbusds.oauth2.sdk.ErrorObject; import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
import com.nimbusds.oauth2.sdk.ParseException; import org.apache.commons.lang3.StringUtils;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
@@ -59,27 +50,35 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRe
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; 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.AbstractOAuth2Token;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType; 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.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; 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.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestOperations; 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 class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
{ {
private static final Log LOGGER = LogFactory.getLog(SpringBasedIdentityServiceFacade.class); private static final Log LOGGER = LogFactory.getLog(SpringBasedIdentityServiceFacade.class);
private static final Instant SOME_INSIGNIFICANT_DATE_IN_THE_PAST = Instant.MIN.plusSeconds(12345); private static final Instant SOME_INSIGNIFICANT_DATE_IN_THE_PAST = Instant.MIN.plusSeconds(12345);
private final Map<AuthorizationGrantType, OAuth2AccessTokenResponseClient> clients; private final Map<AuthorizationGrantType, OAuth2AccessTokenResponseClient> clients;
private final DefaultOAuth2UserService defaultOAuth2UserService;
private final ClientRegistration clientRegistration; private final ClientRegistration clientRegistration;
private final JwtDecoder jwtDecoder; private final JwtDecoder jwtDecoder;
@@ -93,6 +92,7 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
AuthorizationGrantType.AUTHORIZATION_CODE, createAuthorizationCodeClient(restOperations), AuthorizationGrantType.AUTHORIZATION_CODE, createAuthorizationCodeClient(restOperations),
AuthorizationGrantType.REFRESH_TOKEN, createRefreshTokenClient(restOperations), AuthorizationGrantType.REFRESH_TOKEN, createRefreshTokenClient(restOperations),
AuthorizationGrantType.PASSWORD, createPasswordClient(restOperations, clientRegistration)); AuthorizationGrantType.PASSWORD, createPasswordClient(restOperations, clientRegistration));
this.defaultOAuth2UserService = createOAuth2UserService(restOperations);
} }
@Override @Override
@@ -121,51 +121,18 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
} }
@Override @Override
public Optional<OIDCUserInfo> getUserInfo(String tokenParameter, String principalAttribute) public Optional<DecodedTokenUser> getUserInfo(String token, UserInfoAttrMapping userInfoAttrMapping)
{ {
return Optional.ofNullable(tokenParameter) try
.filter(Predicate.not(String::isEmpty)) {
.flatMap(token -> Optional.ofNullable(clientRegistration) return Optional.ofNullable(defaultOAuth2UserService.loadUser(new OAuth2UserRequest(clientRegistration, getSpringAccessToken(token))))
.map(ClientRegistration::getProviderDetails) .flatMap(oAuth2User -> mapOAuth2UserToDecodedTokenUser(oAuth2User, userInfoAttrMapping));
.map(ClientRegistration.ProviderDetails::getUserInfoEndpoint) }
.map(ClientRegistration.ProviderDetails.UserInfoEndpoint::getUri) catch (OAuth2AuthenticationException exception)
.flatMap(uri -> { {
try LOGGER.warn("User Info Request failed: " + exception.getMessage());
{ return Optional.empty();
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
{
UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse);
if (userInfoResponse instanceof UserInfoErrorResponse userInfoErrorResponse)
{
String errorMessage = Optional.ofNullable(userInfoErrorResponse.getErrorObject())
.map(ErrorObject::getDescription)
.orElse("No error description found");
LOGGER.warn("User Info Request failed: " + errorMessage);
throw new UserInfoException(errorMessage);
}
return Optional.of(userInfoResponse);
}
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 @Override
@@ -202,11 +169,7 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
if (grant.isRefreshToken()) if (grant.isRefreshToken())
{ {
final OAuth2AccessToken expiredAccessToken = new OAuth2AccessToken( final OAuth2AccessToken expiredAccessToken = getSpringAccessToken("JUST_FOR_FULFILLING_THE_SPRING_API");
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); final OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(grant.getRefreshToken(), null);
return new OAuth2RefreshTokenGrantRequest(clientRegistration, expiredAccessToken, refreshToken, return new OAuth2RefreshTokenGrantRequest(clientRegistration, expiredAccessToken, refreshToken,
@@ -258,6 +221,26 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
return client; 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)
{
var preferredUsername = Optional.ofNullable(oAuth2User.getAttribute(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME))
.filter(String.class::isInstance)
.map(String.class::cast)
.filter(StringUtils::isNotEmpty);
var 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, private static OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> createPasswordClient(RestOperations rest,
ClientRegistration clientRegistration) ClientRegistration clientRegistration)
{ {
@@ -288,6 +271,16 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
}; };
} }
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 static class SpringAccessTokenAuthorization implements AccessTokenAuthorization
{ {
private final OAuth2AccessTokenResponse tokenResponse; private final OAuth2AccessTokenResponse tokenResponse;

View File

@@ -37,13 +37,19 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; 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.Scope;
import com.nimbusds.oauth2.sdk.id.Identifier; import com.nimbusds.oauth2.sdk.id.Identifier;
import com.nimbusds.oauth2.sdk.id.State; import com.nimbusds.oauth2.sdk.id.State;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.web.util.UriComponentsBuilder;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator; import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator;
@@ -53,16 +59,9 @@ import org.alfresco.repo.security.authentication.identityservice.IdentityService
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization; 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.AuthorizationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.web.util.UriComponentsBuilder;
/** /**
* An {@link AdminConsoleAuthenticator} implementation to extract an externally authenticated user ID * An {@link AdminConsoleAuthenticator} implementation to extract an externally authenticated user ID or to initiate the OIDC authorization code flow.
* or to initiate the OIDC authorization code flow.
*/ */
public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAuthenticator, ActivateableBean public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAuthenticator, ActivateableBean
{ {
@@ -71,7 +70,6 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN"; 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_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION"; private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
private static final Set<String> SCOPES = Set.of("openid", "profile", "email", "offline_access");
private IdentityServiceConfig identityServiceConfig; private IdentityServiceConfig identityServiceConfig;
private IdentityServiceFacade identityServiceFacade; private IdentityServiceFacade identityServiceFacade;
@@ -145,7 +143,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
try try
{ {
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize( AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
authorizationCode(code, request.getRequestURL().toString())); authorizationCode(code, request.getRequestURL().toString()));
addCookies(response, accessTokenAuthorization); addCookies(response, accessTokenAuthorization);
bearerToken = accessTokenAuthorization.getAccessToken().getTokenValue(); bearerToken = accessTokenAuthorization.getAccessToken().getTokenValue();
} }
@@ -154,8 +152,8 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
if (LOGGER.isWarnEnabled()) if (LOGGER.isWarnEnabled())
{ {
LOGGER.warn( LOGGER.warn(
"Error while trying to retrieve a response using the Authorization Code at the Token Endpoint: {}", "Error while trying to retrieve a response using the Authorization Code at the Token Endpoint: {}",
exception.getMessage()); exception.getMessage());
} }
} }
return bearerToken; return bearerToken;
@@ -188,7 +186,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
{ {
cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken().getTokenValue(), response); cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken().getTokenValue(), response);
cookiesService.addCookie(ALFRESCO_TOKEN_EXPIRATION, String.valueOf( cookiesService.addCookie(ALFRESCO_TOKEN_EXPIRATION, String.valueOf(
accessTokenAuthorization.getAccessToken().getExpiresAt().toEpochMilli()), response); accessTokenAuthorization.getAccessToken().getExpiresAt().toEpochMilli()), response);
cookiesService.addCookie(ALFRESCO_REFRESH_TOKEN, accessTokenAuthorization.getRefreshTokenValue(), response); cookiesService.addCookie(ALFRESCO_REFRESH_TOKEN, accessTokenAuthorization.getRefreshTokenValue(), response);
} }
@@ -198,13 +196,13 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
State state = new State(); State state = new State();
UriComponentsBuilder authRequestBuilder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getAuthorizationUri()) UriComponentsBuilder authRequestBuilder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getAuthorizationUri())
.queryParam("client_id", clientRegistration.getClientId()) .queryParam("client_id", clientRegistration.getClientId())
.queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString())) .queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString()))
.queryParam("response_type", "code") .queryParam("response_type", "code")
.queryParam("scope", String.join("+", getScopes(clientRegistration))) .queryParam("scope", String.join("+", getScopes(clientRegistration)))
.queryParam("state", state.toString()); .queryParam("state", state.toString());
if(StringUtils.isNotBlank(identityServiceConfig.getAudience())) if (StringUtils.isNotBlank(identityServiceConfig.getAudience()))
{ {
authRequestBuilder.queryParam("audience", identityServiceConfig.getAudience()); authRequestBuilder.queryParam("audience", identityServiceConfig.getAudience());
} }
@@ -215,20 +213,25 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
private Set<String> getScopes(ClientRegistration clientRegistration) private Set<String> getScopes(ClientRegistration clientRegistration)
{ {
return Optional.ofNullable(clientRegistration.getProviderDetails()) return Optional.ofNullable(clientRegistration.getProviderDetails())
.map(ProviderDetails::getConfigurationMetadata) .map(ProviderDetails::getConfigurationMetadata)
.map(metadata -> metadata.get(SCOPES_SUPPORTED.getValue())) .map(metadata -> metadata.get(SCOPES_SUPPORTED.getValue()))
.filter(Scope.class::isInstance) .filter(Scope.class::isInstance)
.map(Scope.class::cast) .map(Scope.class::cast)
.map(this::getSupportedScopes) .map(this::getSupportedScopes)
.orElse(clientRegistration.getScopes()); .orElse(clientRegistration.getScopes());
} }
private Set<String> getSupportedScopes(Scope scopes) private Set<String> getSupportedScopes(Scope scopes)
{ {
return scopes.stream() return scopes.stream()
.filter(scope -> SCOPES.contains(scope.getValue())) .filter(this::hasAdminConsoleScope)
.map(Identifier::getValue) .map(Identifier::getValue)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
}
private boolean hasAdminConsoleScope(Scope.Value scope)
{
return identityServiceConfig.getAdminConsoleScopes().contains(scope.getValue());
} }
private String getRedirectUri(String requestURL) private String getRedirectUri(String requestURL)
@@ -263,7 +266,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
private AccessTokenAuthorization doRefreshAuthToken(String refreshToken) private AccessTokenAuthorization doRefreshAuthToken(String refreshToken)
{ {
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize( AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
AuthorizationGrant.refreshToken(refreshToken)); AuthorizationGrant.refreshToken(refreshToken));
if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null) if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null)
{ {
throw new AuthenticationException("AccessTokenResponse is null or empty"); throw new AuthenticationException("AccessTokenResponse is null or empty");
@@ -284,7 +287,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
} }
public void setIdentityServiceFacade( public void setIdentityServiceFacade(
IdentityServiceFacade identityServiceFacade) IdentityServiceFacade identityServiceFacade)
{ {
this.identityServiceFacade = identityServiceFacade; this.identityServiceFacade = identityServiceFacade;
} }
@@ -295,13 +298,13 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
} }
public void setCookiesService( public void setCookiesService(
AdminConsoleAuthenticationCookiesService cookiesService) AdminConsoleAuthenticationCookiesService cookiesService)
{ {
this.cookiesService = cookiesService; this.cookiesService = cookiesService;
} }
public void setIdentityServiceConfig( public void setIdentityServiceConfig(
IdentityServiceConfig identityServiceConfig) IdentityServiceConfig identityServiceConfig)
{ {
this.identityServiceConfig = identityServiceConfig; this.identityServiceConfig = identityServiceConfig;
} }
@@ -316,4 +319,4 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
{ {
this.isEnabled = isEnabled; this.isEnabled = isEnabled;
} }
} }

View File

@@ -0,0 +1,66 @@
/*
* #%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.user;
import java.util.Optional;
import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
import org.apache.commons.lang3.StringUtils;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
public class AccessTokenToDecodedTokenUserMapper
{
private static final String DEFAULT_USERNAME_CLAIM = PersonClaims.PREFERRED_USERNAME_CLAIM_NAME;
private final UserInfoAttrMapping userInfoAttrMapping;
public AccessTokenToDecodedTokenUserMapper(UserInfoAttrMapping userInfoAttrMapping)
{
this.userInfoAttrMapping = userInfoAttrMapping;
}
/**
* Maps the given {@link IdentityServiceFacade.DecodedAccessToken} to a {@link DecodedTokenUser}.
*
* @param token
* the token to map
* @return the mapped {@link DecodedTokenUser} or {@link Optional#empty()} if the token does not contain a username claim
*/
public Optional<DecodedTokenUser> toDecodedTokenUser(IdentityServiceFacade.DecodedAccessToken token)
{
Object firstName = token.getClaim(userInfoAttrMapping.firstNameClaim());
Object lastName = token.getClaim(userInfoAttrMapping.lastNameClaim());
Object email = token.getClaim(userInfoAttrMapping.emailClaim());
return Optional.ofNullable(token.getClaim(Optional.ofNullable(userInfoAttrMapping.usernameClaim())
.filter(StringUtils::isNotBlank)
.orElse(DEFAULT_USERNAME_CLAIM)))
.filter(String.class::isInstance)
.map(String.class::cast)
.map(username -> DecodedTokenUser.validateAndCreate(username, firstName, lastName, email));
}
}

View File

@@ -0,0 +1,44 @@
/*
* #%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.user;
import java.util.Optional;
public record DecodedTokenUser(String username, String firstName, String lastName, String email)
{
private static final String EMPTY_STRING = "";
public static DecodedTokenUser validateAndCreate(String username, Object firstName, Object lastName, Object email)
{
return new DecodedTokenUser(username, getStringVal(firstName), getStringVal(lastName), getStringVal(email));
}
private static String getStringVal(Object firstName)
{
return Optional.ofNullable(firstName).filter(String.class::isInstance).map(String.class::cast).orElse(EMPTY_STRING);
}
}

View File

@@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.security.authentication.identityservice; package org.alfresco.repo.security.authentication.identityservice.user;
import java.util.stream.Stream; import java.util.stream.Stream;

View File

@@ -0,0 +1,76 @@
/*
* #%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.user;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.security.PersonService;
public class TokenUserToOIDCUserMapper
{
private final PersonService personService;
public TokenUserToOIDCUserMapper(PersonService personService)
{
this.personService = personService;
}
/**
* Maps a decoded token user to an OIDC user where the user id (username) is normalized.
*
* @param decodedTokenUser
* the decoded token user
* @return the OIDC user
*/
public OIDCUserInfo toOIDCUser(DecodedTokenUser decodedTokenUser)
{
return new OIDCUserInfo(usernameToUserId(decodedTokenUser.username()), decodedTokenUser.firstName(), decodedTokenUser.lastName(), decodedTokenUser.email());
}
/**
* Normalizes a username, taking into account existing user accounts and case sensitivity settings.
*
* @param caseSensitiveUserName
* the case-sensitive username
* @return the string
*/
private String usernameToUserId(final String caseSensitiveUserName)
{
if (caseSensitiveUserName == null)
{
return null;
}
String normalized = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<String>() {
@Override
public String doWork() throws Exception
{
return personService.getUserIdentifier(caseSensitiveUserName);
}
}, AuthenticationUtil.getSystemUserName());
return normalized == null ? caseSensitiveUserName : normalized;
}
}

View File

@@ -0,0 +1,41 @@
/*
* #%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.user;
/**
* The UserInfoAttrMapping record represents the mapping of claims fetched from the UserInfo endpoint to create an Alfresco user.
*
* @param usernameClaim
* the claim that represents the username
* @param firstNameClaim
* the claim that represents the first name
* @param lastNameClaim
* the claim that represents the last name
* @param emailClaim
* the claim that represents the email
*/
public record UserInfoAttrMapping(String usernameClaim, String firstNameClaim, String lastNameClaim, String emailClaim)
{}

View File

@@ -82,6 +82,7 @@
<property name="contentService" ref="contentService" /> <property name="contentService" ref="contentService" />
<property name="renditionService2" ref="renditionService2" /> <property name="renditionService2" ref="renditionService2" />
<property name="directAccessUrlEnabled" value="${local.transform.directAccessUrl.enabled}"/> <property name="directAccessUrlEnabled" value="${local.transform.directAccessUrl.enabled}"/>
<property name="threadPoolSize" value="${local.transform.threadPoolSize}" />
</bean> </bean>
<bean id="synchronousTransformClient" parent="localSynchronousTransformClient" /> <bean id="synchronousTransformClient" parent="localSynchronousTransformClient" />

View File

@@ -1351,6 +1351,9 @@ restApi.directAccessUrl.defaultExpiryTimeInSec=30
# Controls whether direct access url URLs may be used in transforms. # Controls whether direct access url URLs may be used in transforms.
local.transform.directAccessUrl.enabled=true local.transform.directAccessUrl.enabled=true
# Controls size of thread pool used for transforms.
local.transform.threadPoolSize=8
# Creates additional indexes on alf_node and alf_transaction. Recommended for large repositories. # Creates additional indexes on alf_node and alf_transaction. Recommended for large repositories.
system.new-node-transaction-indexes.ignored=true system.new-node-transaction-indexes.ignored=true

View File

@@ -149,6 +149,15 @@
<property name="principalAttribute"> <property name="principalAttribute">
<value>${identity-service.principal-attribute:preferred_username}</value> <value>${identity-service.principal-attribute:preferred_username}</value>
</property> </property>
<property name="firstNameAttribute">
<value>${identity-service.first-name-attribute:given_name}</value>
</property>
<property name="lastNameAttribute">
<value>${identity-service.last-name-attribute:family_name}</value>
</property>
<property name="emailAttribute">
<value>${identity-service.email-attribute:email}</value>
</property>
<property name="clientIdValidationDisabled"> <property name="clientIdValidationDisabled">
<value>${identity-service.client-id.validation.disabled:true}</value> <value>${identity-service.client-id.validation.disabled:true}</value>
</property> </property>
@@ -158,6 +167,18 @@
<property name="signatureAlgorithms"> <property name="signatureAlgorithms">
<value>${identity-service.signature-algorithms:RS256,PS256}</value> <value>${identity-service.signature-algorithms:RS256,PS256}</value>
</property> </property>
<property name="adminConsoleScopes">
<value>${identity-service.admin-console.scopes:openid,profile,email,offline_access}</value>
</property>
<property name="passwordGrantScopes">
<value>${identity-service.password-grant.scopes:openid,profile,email}</value>
</property>
<property name="issuerAttribute">
<value>${identity-service.issuer-attribute:issuer}</value>
</property>
<property name="jwtClockSkewMs">
<value>${identity-service.jwt-clock-skew-ms:0}</value>
</property>
</bean> </bean>
<!-- Enable control over mapping between request and user ID --> <!-- Enable control over mapping between request and user ID -->
@@ -219,4 +240,4 @@
<ref bean="transactionService" /> <ref bean="transactionService" />
</property> </property>
</bean> </bean>
</beans> </beans>

View File

@@ -12,4 +12,11 @@ identity-service.resource=alfresco
identity-service.credentials.secret= identity-service.credentials.secret=
identity-service.public-client=true identity-service.public-client=true
identity-service.admin-console.redirect-path=/alfresco/s/admin/admin-communitysummary identity-service.admin-console.redirect-path=/alfresco/s/admin/admin-communitysummary
identity-service.signature-algorithms=RS256,PS256 identity-service.signature-algorithms=RS256,PS256
identity-service.first-name-attribute=given_name
identity-service.last-name-attribute=family_name
identity-service.email-attribute=email
identity-service.admin-console.scopes=openid,profile,email,offline_access
identity-service.password-grant.scopes=openid,profile,email
identity-service.issuer-attribute=issuer
identity-service.jwt-clock-skew-ms=0

View File

@@ -4,27 +4,31 @@
* %% * %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco; package org.alfresco;
import org.junit.experimental.categories.Categories;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.alfresco.repo.security.authentication.identityservice.ClientRegistrationProviderUnitTest; import org.alfresco.repo.security.authentication.identityservice.ClientRegistrationProviderUnitTest;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBeanTest; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBeanTest;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceJITProvisioningHandlerUnitTest; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceJITProvisioningHandlerUnitTest;
@@ -33,237 +37,236 @@ import org.alfresco.repo.security.authentication.identityservice.SpringBasedIden
import org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleAuthenticationCookiesServiceUnitTest; import org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleAuthenticationCookiesServiceUnitTest;
import org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleHttpServletRequestWrapperUnitTest; import org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleHttpServletRequestWrapperUnitTest;
import org.alfresco.repo.security.authentication.identityservice.admin.IdentityServiceAdminConsoleAuthenticatorUnitTest; import org.alfresco.repo.security.authentication.identityservice.admin.IdentityServiceAdminConsoleAuthenticatorUnitTest;
import org.alfresco.repo.security.authentication.identityservice.user.AccessTokenToDecodedTokenUserMapperUnitTest;
import org.alfresco.repo.security.authentication.identityservice.user.TokenUserToOIDCUserMapperUnitTest;
import org.alfresco.util.testing.category.DBTests; import org.alfresco.util.testing.category.DBTests;
import org.alfresco.util.testing.category.NonBuildTests; import org.alfresco.util.testing.category.NonBuildTests;
import org.junit.experimental.categories.Categories;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/** /**
* All Repository project UNIT test classes (no application context) should be added to this test suite. * All Repository project UNIT test classes (no application context) should be added to this test suite. Tests marked as DBTests are automatically excluded and are run as part of {@link AllDBTestsTestSuite}.
* Tests marked as DBTests are automatically excluded and are run as part of {@link AllDBTestsTestSuite}.
*/ */
@RunWith(Categories.class) @RunWith(Categories.class)
@Categories.ExcludeCategory({DBTests.class, NonBuildTests.class}) @Categories.ExcludeCategory({DBTests.class, NonBuildTests.class})
@Suite.SuiteClasses({ @Suite.SuiteClasses(value = {
org.alfresco.repo.site.SiteMembershipTest.class, org.alfresco.repo.site.SiteMembershipTest.class,
org.alfresco.encryption.EncryptorTest.class, org.alfresco.encryption.EncryptorTest.class,
org.alfresco.encryption.KeyStoreKeyProviderTest.class, org.alfresco.encryption.KeyStoreKeyProviderTest.class,
org.alfresco.filesys.config.ServerConfigurationBeanTest.class, org.alfresco.filesys.config.ServerConfigurationBeanTest.class,
org.alfresco.filesys.repo.rules.ShuffleTest.class, org.alfresco.filesys.repo.rules.ShuffleTest.class,
org.alfresco.opencmis.AlfrescoCmisExceptionInterceptorTest.class, org.alfresco.opencmis.AlfrescoCmisExceptionInterceptorTest.class,
org.alfresco.repo.admin.Log4JHierarchyInitTest.class, org.alfresco.repo.admin.Log4JHierarchyInitTest.class,
org.alfresco.repo.attributes.PropTablesCleanupJobTest.class, org.alfresco.repo.attributes.PropTablesCleanupJobTest.class,
org.alfresco.repo.cache.AbstractCacheFactoryTest.class, org.alfresco.repo.cache.AbstractCacheFactoryTest.class,
org.alfresco.repo.cache.DefaultCacheFactoryTest.class, org.alfresco.repo.cache.DefaultCacheFactoryTest.class,
org.alfresco.repo.cache.DefaultSimpleCacheTest.class, org.alfresco.repo.cache.DefaultSimpleCacheTest.class,
org.alfresco.repo.cache.InMemoryCacheStatisticsTest.class, org.alfresco.repo.cache.InMemoryCacheStatisticsTest.class,
org.alfresco.repo.cache.TransactionStatsTest.class, org.alfresco.repo.cache.TransactionStatsTest.class,
org.alfresco.repo.cache.lookup.EntityLookupCacheTest.class, org.alfresco.repo.cache.lookup.EntityLookupCacheTest.class,
org.alfresco.repo.calendar.CalendarHelpersTest.class, org.alfresco.repo.calendar.CalendarHelpersTest.class,
org.alfresco.repo.copy.CopyServiceImplUnitTest.class, org.alfresco.repo.copy.CopyServiceImplUnitTest.class,
org.alfresco.repo.dictionary.RepoDictionaryDAOTest.class, org.alfresco.repo.dictionary.RepoDictionaryDAOTest.class,
org.alfresco.repo.forms.processor.node.FieldProcessorTest.class, org.alfresco.repo.forms.processor.node.FieldProcessorTest.class,
org.alfresco.repo.forms.processor.workflow.TaskFormProcessorTest.class, org.alfresco.repo.forms.processor.workflow.TaskFormProcessorTest.class,
org.alfresco.repo.forms.processor.workflow.WorkflowFormProcessorTest.class, org.alfresco.repo.forms.processor.workflow.WorkflowFormProcessorTest.class,
org.alfresco.repo.invitation.site.InviteSenderTest.class, org.alfresco.repo.invitation.site.InviteSenderTest.class,
org.alfresco.repo.invitation.site.InviteModeratedSenderTest.class, org.alfresco.repo.invitation.site.InviteModeratedSenderTest.class,
org.alfresco.repo.jscript.ScriptSearchTest.class, org.alfresco.repo.jscript.ScriptSearchTest.class,
org.alfresco.repo.lock.LockUtilsTest.class, org.alfresco.repo.lock.LockUtilsTest.class,
org.alfresco.repo.lock.mem.LockStoreImplTest.class, org.alfresco.repo.lock.mem.LockStoreImplTest.class,
org.alfresco.repo.management.CheckRequiredClassesForLoggingConsoleUnitTest.class, org.alfresco.repo.management.CheckRequiredClassesForLoggingConsoleUnitTest.class,
org.alfresco.repo.management.subsystems.CryptodocSwitchableApplicationContextFactoryTest.class, org.alfresco.repo.management.subsystems.CryptodocSwitchableApplicationContextFactoryTest.class,
org.alfresco.repo.module.ModuleDetailsImplTest.class, org.alfresco.repo.module.ModuleDetailsImplTest.class,
org.alfresco.repo.module.ModuleVersionNumberTest.class, org.alfresco.repo.module.ModuleVersionNumberTest.class,
org.alfresco.repo.module.DeprecatedModulesValidatorTest.class, org.alfresco.repo.module.DeprecatedModulesValidatorTest.class,
org.alfresco.repo.node.integrity.IntegrityEventTest.class, org.alfresco.repo.node.integrity.IntegrityEventTest.class,
org.alfresco.repo.policy.MTPolicyComponentTest.class, org.alfresco.repo.policy.MTPolicyComponentTest.class,
org.alfresco.repo.policy.PolicyComponentTest.class, org.alfresco.repo.policy.PolicyComponentTest.class,
org.alfresco.repo.rendition.RenditionNodeManagerTest.class, org.alfresco.repo.rendition.RenditionNodeManagerTest.class,
org.alfresco.repo.rendition.RenditionServiceImplTest.class, org.alfresco.repo.rendition.RenditionServiceImplTest.class,
org.alfresco.repo.replication.ReplicationServiceImplTest.class, org.alfresco.repo.replication.ReplicationServiceImplTest.class,
org.alfresco.repo.rule.RuleServiceImplUnitTest.class, org.alfresco.repo.rule.RuleServiceImplUnitTest.class,
org.alfresco.repo.service.StoreRedirectorProxyFactoryTest.class, org.alfresco.repo.service.StoreRedirectorProxyFactoryTest.class,
org.alfresco.repo.site.RoleComparatorImplTest.class, org.alfresco.repo.site.RoleComparatorImplTest.class,
org.alfresco.repo.template.UnsafeMethodsTest.class, org.alfresco.repo.template.UnsafeMethodsTest.class,
org.alfresco.repo.tenant.MultiTAdminServiceImplTest.class, org.alfresco.repo.tenant.MultiTAdminServiceImplTest.class,
org.alfresco.repo.thumbnail.ThumbnailServiceImplParameterTest.class, org.alfresco.repo.thumbnail.ThumbnailServiceImplParameterTest.class,
org.alfresco.repo.transfer.ContentChunkerImplTest.class, org.alfresco.repo.transfer.ContentChunkerImplTest.class,
org.alfresco.repo.transfer.HttpClientTransmitterImplTest.class, org.alfresco.repo.transfer.HttpClientTransmitterImplTest.class,
org.alfresco.repo.transfer.manifest.TransferManifestTest.class, org.alfresco.repo.transfer.manifest.TransferManifestTest.class,
org.alfresco.repo.transfer.TransferVersionCheckerImplTest.class, org.alfresco.repo.transfer.TransferVersionCheckerImplTest.class,
org.alfresco.service.cmr.calendar.CalendarRecurrenceHelperTest.class, org.alfresco.service.cmr.calendar.CalendarRecurrenceHelperTest.class,
org.alfresco.service.cmr.calendar.CalendarTimezoneHelperTest.class, org.alfresco.service.cmr.calendar.CalendarTimezoneHelperTest.class,
org.alfresco.tools.RenameUserTest.class, org.alfresco.tools.RenameUserTest.class,
org.alfresco.util.VersionNumberTest.class, org.alfresco.util.VersionNumberTest.class,
org.alfresco.util.FileNameValidatorTest.class, org.alfresco.util.FileNameValidatorTest.class,
org.alfresco.util.HttpClientHelperTest.class, org.alfresco.util.HttpClientHelperTest.class,
org.alfresco.util.JSONtoFmModelTest.class, org.alfresco.util.JSONtoFmModelTest.class,
org.alfresco.util.ModelUtilTest.class, org.alfresco.util.ModelUtilTest.class,
org.alfresco.util.PropertyMapTest.class, org.alfresco.util.PropertyMapTest.class,
org.alfresco.util.ValueProtectingMapTest.class, org.alfresco.util.ValueProtectingMapTest.class,
org.alfresco.util.json.ExceptionJsonSerializerTest.class, org.alfresco.util.json.ExceptionJsonSerializerTest.class,
org.alfresco.util.collections.CollectionUtilsTest.class, org.alfresco.util.collections.CollectionUtilsTest.class,
org.alfresco.util.schemacomp.DbObjectXMLTransformerTest.class, org.alfresco.util.schemacomp.DbObjectXMLTransformerTest.class,
org.alfresco.util.schemacomp.DbPropertyTest.class, org.alfresco.util.schemacomp.DbPropertyTest.class,
org.alfresco.util.schemacomp.DefaultComparisonUtilsTest.class, org.alfresco.util.schemacomp.DefaultComparisonUtilsTest.class,
org.alfresco.util.schemacomp.DifferenceTest.class, org.alfresco.util.schemacomp.DifferenceTest.class,
org.alfresco.util.schemacomp.MultiFileDumperTest.class, org.alfresco.util.schemacomp.MultiFileDumperTest.class,
org.alfresco.util.schemacomp.RedundantDbObjectTest.class, org.alfresco.util.schemacomp.RedundantDbObjectTest.class,
org.alfresco.util.schemacomp.SchemaComparatorTest.class, org.alfresco.util.schemacomp.SchemaComparatorTest.class,
org.alfresco.util.schemacomp.SchemaToXMLTest.class, org.alfresco.util.schemacomp.SchemaToXMLTest.class,
org.alfresco.util.schemacomp.ValidatingVisitorTest.class, org.alfresco.util.schemacomp.ValidatingVisitorTest.class,
org.alfresco.util.schemacomp.ValidationResultTest.class, org.alfresco.util.schemacomp.ValidationResultTest.class,
org.alfresco.util.schemacomp.XMLToSchemaTest.class, org.alfresco.util.schemacomp.XMLToSchemaTest.class,
org.alfresco.util.schemacomp.model.ColumnTest.class, org.alfresco.util.schemacomp.model.ColumnTest.class,
org.alfresco.util.schemacomp.model.ForeignKeyTest.class, org.alfresco.util.schemacomp.model.ForeignKeyTest.class,
org.alfresco.util.schemacomp.model.IndexTest.class, org.alfresco.util.schemacomp.model.IndexTest.class,
org.alfresco.util.schemacomp.model.PrimaryKeyTest.class, org.alfresco.util.schemacomp.model.PrimaryKeyTest.class,
org.alfresco.util.schemacomp.model.SchemaTest.class, org.alfresco.util.schemacomp.model.SchemaTest.class,
org.alfresco.util.schemacomp.model.SequenceTest.class, org.alfresco.util.schemacomp.model.SequenceTest.class,
org.alfresco.util.schemacomp.model.TableTest.class, org.alfresco.util.schemacomp.model.TableTest.class,
org.alfresco.util.schemacomp.validator.IndexColumnsValidatorTest.class, org.alfresco.util.schemacomp.validator.IndexColumnsValidatorTest.class,
org.alfresco.util.schemacomp.validator.NameValidatorTest.class, org.alfresco.util.schemacomp.validator.NameValidatorTest.class,
org.alfresco.util.schemacomp.validator.SchemaVersionValidatorTest.class, org.alfresco.util.schemacomp.validator.SchemaVersionValidatorTest.class,
org.alfresco.util.schemacomp.validator.TypeNameOnlyValidatorTest.class, org.alfresco.util.schemacomp.validator.TypeNameOnlyValidatorTest.class,
org.alfresco.util.test.OmittedTestClassFinderUnitTest.class, org.alfresco.util.test.OmittedTestClassFinderUnitTest.class,
org.alfresco.util.test.junitrules.RetryAtMostRuleTest.class, org.alfresco.util.test.junitrules.RetryAtMostRuleTest.class,
org.alfresco.util.test.junitrules.TemporaryMockOverrideTest.class, org.alfresco.util.test.junitrules.TemporaryMockOverrideTest.class,
org.alfresco.repo.search.impl.solr.AbstractSolrQueryHTTPClientTest.class, org.alfresco.repo.search.impl.solr.AbstractSolrQueryHTTPClientTest.class,
org.alfresco.repo.search.impl.solr.SpellCheckDecisionManagerTest.class, org.alfresco.repo.search.impl.solr.SpellCheckDecisionManagerTest.class,
org.alfresco.repo.search.impl.solr.SolrStoreMappingWrapperTest.class, org.alfresco.repo.search.impl.solr.SolrStoreMappingWrapperTest.class,
org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngineTest.class, org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngineTest.class,
org.alfresco.repo.search.impl.querymodel.impl.db.NodePermissionAssessorLimitsTest.class, org.alfresco.repo.search.impl.querymodel.impl.db.NodePermissionAssessorLimitsTest.class,
org.alfresco.repo.search.impl.querymodel.impl.db.NodePermissionAssessorPermissionsTest.class, org.alfresco.repo.search.impl.querymodel.impl.db.NodePermissionAssessorPermissionsTest.class,
org.alfresco.repo.search.impl.solr.DbOrIndexSwitchingQueryLanguageTest.class, org.alfresco.repo.search.impl.solr.DbOrIndexSwitchingQueryLanguageTest.class,
org.alfresco.repo.search.impl.solr.SolrQueryHTTPClientTest.class, org.alfresco.repo.search.impl.solr.SolrQueryHTTPClientTest.class,
org.alfresco.repo.search.impl.solr.SolrSQLHttpClientTest.class, org.alfresco.repo.search.impl.solr.SolrSQLHttpClientTest.class,
org.alfresco.repo.search.impl.solr.SolrStatsResultTest.class, org.alfresco.repo.search.impl.solr.SolrStatsResultTest.class,
org.alfresco.repo.search.impl.solr.SolrJSONResultTest.class, org.alfresco.repo.search.impl.solr.SolrJSONResultTest.class,
org.alfresco.repo.search.impl.solr.SolrSQLJSONResultMetadataSetTest.class, org.alfresco.repo.search.impl.solr.SolrSQLJSONResultMetadataSetTest.class,
org.alfresco.repo.search.impl.solr.facet.SolrFacetComparatorTest.class, org.alfresco.repo.search.impl.solr.facet.SolrFacetComparatorTest.class,
org.alfresco.repo.search.impl.solr.facet.FacetQNameUtilsTest.class, org.alfresco.repo.search.impl.solr.facet.FacetQNameUtilsTest.class,
org.alfresco.util.BeanExtenderUnitTest.class, org.alfresco.util.BeanExtenderUnitTest.class,
org.alfresco.repo.solr.SOLRTrackingComponentUnitTest.class, org.alfresco.repo.solr.SOLRTrackingComponentUnitTest.class,
IdentityServiceFacadeFactoryBeanTest.class, IdentityServiceFacadeFactoryBeanTest.class,
LazyInstantiatingIdentityServiceFacadeUnitTest.class, LazyInstantiatingIdentityServiceFacadeUnitTest.class,
SpringBasedIdentityServiceFacadeUnitTest.class, SpringBasedIdentityServiceFacadeUnitTest.class,
IdentityServiceJITProvisioningHandlerUnitTest.class, IdentityServiceJITProvisioningHandlerUnitTest.class,
AdminConsoleAuthenticationCookiesServiceUnitTest.class, AccessTokenToDecodedTokenUserMapperUnitTest.class,
AdminConsoleHttpServletRequestWrapperUnitTest.class, TokenUserToOIDCUserMapperUnitTest.class,
IdentityServiceAdminConsoleAuthenticatorUnitTest.class, AdminConsoleAuthenticationCookiesServiceUnitTest.class,
ClientRegistrationProviderUnitTest.class, AdminConsoleHttpServletRequestWrapperUnitTest.class,
org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class, IdentityServiceAdminConsoleAuthenticatorUnitTest.class,
org.alfresco.repo.security.authentication.PasswordHashingTest.class, ClientRegistrationProviderUnitTest.class,
org.alfresco.repo.security.authority.script.ScriptAuthorityService_RegExTest.class, org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class,
org.alfresco.repo.security.permissions.PermissionCheckCollectionTest.class, org.alfresco.repo.security.authentication.PasswordHashingTest.class,
org.alfresco.repo.security.sync.LDAPUserRegistryTest.class, org.alfresco.repo.security.authority.script.ScriptAuthorityService_RegExTest.class,
org.alfresco.traitextender.TraitExtenderIntegrationTest.class, org.alfresco.repo.security.permissions.PermissionCheckCollectionTest.class,
org.alfresco.traitextender.AJExtensionsCompileTest.class, org.alfresco.repo.security.sync.LDAPUserRegistryTest.class,
org.alfresco.traitextender.TraitExtenderIntegrationTest.class,
org.alfresco.traitextender.AJExtensionsCompileTest.class,
org.alfresco.repo.virtual.page.PageCollatorTest.class, org.alfresco.repo.virtual.page.PageCollatorTest.class,
org.alfresco.repo.virtual.ref.GetChildByIdMethodTest.class, org.alfresco.repo.virtual.ref.GetChildByIdMethodTest.class,
org.alfresco.repo.virtual.ref.GetParentReferenceMethodTest.class, org.alfresco.repo.virtual.ref.GetParentReferenceMethodTest.class,
org.alfresco.repo.virtual.ref.NewVirtualReferenceMethodTest.class, org.alfresco.repo.virtual.ref.NewVirtualReferenceMethodTest.class,
org.alfresco.repo.virtual.ref.PlainReferenceParserTest.class, org.alfresco.repo.virtual.ref.PlainReferenceParserTest.class,
org.alfresco.repo.virtual.ref.PlainStringifierTest.class, org.alfresco.repo.virtual.ref.PlainStringifierTest.class,
org.alfresco.repo.virtual.ref.ProtocolTest.class, org.alfresco.repo.virtual.ref.ProtocolTest.class,
org.alfresco.repo.virtual.ref.ReferenceTest.class, org.alfresco.repo.virtual.ref.ReferenceTest.class,
org.alfresco.repo.virtual.ref.ResourceParameterTest.class, org.alfresco.repo.virtual.ref.ResourceParameterTest.class,
org.alfresco.repo.virtual.ref.StringParameterTest.class, org.alfresco.repo.virtual.ref.StringParameterTest.class,
org.alfresco.repo.virtual.ref.VirtualProtocolTest.class, org.alfresco.repo.virtual.ref.VirtualProtocolTest.class,
org.alfresco.repo.virtual.store.ReferenceComparatorTest.class, org.alfresco.repo.virtual.store.ReferenceComparatorTest.class,
org.alfresco.repo.virtual.ref.ZeroReferenceParserTest.class, org.alfresco.repo.virtual.ref.ZeroReferenceParserTest.class,
org.alfresco.repo.virtual.ref.ZeroStringifierTest.class, org.alfresco.repo.virtual.ref.ZeroStringifierTest.class,
org.alfresco.repo.virtual.ref.HashStringifierTest.class, org.alfresco.repo.virtual.ref.HashStringifierTest.class,
org.alfresco.repo.virtual.ref.NodeRefRadixHasherTest.class, org.alfresco.repo.virtual.ref.NodeRefRadixHasherTest.class,
org.alfresco.repo.virtual.ref.NumericPathHasherTest.class, org.alfresco.repo.virtual.ref.NumericPathHasherTest.class,
org.alfresco.repo.virtual.ref.StoredPathHasherTest.class, org.alfresco.repo.virtual.ref.StoredPathHasherTest.class,
org.alfresco.repo.virtual.template.VirtualQueryImplTest.class, org.alfresco.repo.virtual.template.VirtualQueryImplTest.class,
org.alfresco.repo.virtual.store.TypeVirtualizationMethodUnitTest.class, org.alfresco.repo.virtual.store.TypeVirtualizationMethodUnitTest.class,
org.alfresco.repo.security.authentication.AuthenticationServiceImplTest.class, org.alfresco.repo.security.authentication.AuthenticationServiceImplTest.class,
org.alfresco.util.EmailHelperTest.class, org.alfresco.util.EmailHelperTest.class,
org.alfresco.repo.action.ParameterDefinitionImplTest.class, org.alfresco.repo.action.ParameterDefinitionImplTest.class,
org.alfresco.repo.action.ActionDefinitionImplTest.class, org.alfresco.repo.action.ActionDefinitionImplTest.class,
org.alfresco.repo.action.ActionConditionDefinitionImplTest.class, org.alfresco.repo.action.ActionConditionDefinitionImplTest.class,
org.alfresco.repo.action.ActionImplTest.class, org.alfresco.repo.action.ActionImplTest.class,
org.alfresco.repo.action.ActionConditionImplTest.class, org.alfresco.repo.action.ActionConditionImplTest.class,
org.alfresco.repo.action.CompositeActionImplTest.class, org.alfresco.repo.action.CompositeActionImplTest.class,
org.alfresco.repo.action.CompositeActionConditionImplTest.class, org.alfresco.repo.action.CompositeActionConditionImplTest.class,
org.alfresco.repo.action.executer.TransformActionExecuterTest.class, org.alfresco.repo.action.executer.TransformActionExecuterTest.class,
org.alfresco.repo.action.executer.ImporterActionExecutorUnitTest.class, org.alfresco.repo.action.executer.ImporterActionExecutorUnitTest.class,
org.alfresco.repo.audit.AuditableAnnotationTest.class, org.alfresco.repo.audit.AuditableAnnotationTest.class,
org.alfresco.repo.audit.PropertyAuditFilterTest.class, org.alfresco.repo.audit.PropertyAuditFilterTest.class,
org.alfresco.repo.audit.access.NodeChangeTest.class, org.alfresco.repo.audit.access.NodeChangeTest.class,
org.alfresco.repo.content.ContentServiceImplUnitTest.class, org.alfresco.repo.content.ContentServiceImplUnitTest.class,
org.alfresco.repo.content.directurl.SystemWideDirectUrlConfigUnitTest.class, org.alfresco.repo.content.directurl.SystemWideDirectUrlConfigUnitTest.class,
org.alfresco.repo.content.directurl.ContentStoreDirectUrlConfigUnitTest.class, org.alfresco.repo.content.directurl.ContentStoreDirectUrlConfigUnitTest.class,
org.alfresco.repo.content.LimitedStreamCopierTest.class, org.alfresco.repo.content.LimitedStreamCopierTest.class,
org.alfresco.repo.content.filestore.FileIOTest.class, org.alfresco.repo.content.filestore.FileIOTest.class,
org.alfresco.repo.content.filestore.SpoofedTextContentReaderTest.class, org.alfresco.repo.content.filestore.SpoofedTextContentReaderTest.class,
org.alfresco.repo.content.ContentDataTest.class, org.alfresco.repo.content.ContentDataTest.class,
org.alfresco.repo.content.replication.AggregatingContentStoreUnitTest.class, org.alfresco.repo.content.replication.AggregatingContentStoreUnitTest.class,
org.alfresco.service.cmr.repository.TransformationOptionLimitsTest.class, org.alfresco.service.cmr.repository.TransformationOptionLimitsTest.class,
org.alfresco.service.cmr.repository.TransformationOptionPairTest.class, org.alfresco.service.cmr.repository.TransformationOptionPairTest.class,
org.alfresco.repo.content.transform.TransformerConfigTestSuite.class, org.alfresco.repo.content.transform.TransformerConfigTestSuite.class,
org.alfresco.repo.content.transform.TransformerDebugTest.class, org.alfresco.repo.content.transform.TransformerDebugTest.class,
org.alfresco.service.cmr.repository.TemporalSourceOptionsTest.class, org.alfresco.service.cmr.repository.TemporalSourceOptionsTest.class,
org.alfresco.repo.content.metadata.MetadataExtracterLimitsTest.class, org.alfresco.repo.content.metadata.MetadataExtracterLimitsTest.class,
org.alfresco.repo.content.caching.quota.StandardQuotaStrategyMockTest.class, org.alfresco.repo.content.caching.quota.StandardQuotaStrategyMockTest.class,
org.alfresco.repo.content.caching.quota.UnlimitedQuotaStrategyTest.class, org.alfresco.repo.content.caching.quota.UnlimitedQuotaStrategyTest.class,
org.alfresco.repo.content.caching.CachingContentStoreTest.class, org.alfresco.repo.content.caching.CachingContentStoreTest.class,
org.alfresco.repo.content.caching.ContentCacheImplTest.class, org.alfresco.repo.content.caching.ContentCacheImplTest.class,
org.alfresco.repo.domain.permissions.FixedAclUpdaterUnitTest.class, org.alfresco.repo.domain.permissions.FixedAclUpdaterUnitTest.class,
org.alfresco.repo.domain.propval.PropertyTypeConverterTest.class, org.alfresco.repo.domain.propval.PropertyTypeConverterTest.class,
org.alfresco.repo.domain.schema.script.ScriptBundleExecutorImplTest.class, org.alfresco.repo.domain.schema.script.ScriptBundleExecutorImplTest.class,
org.alfresco.repo.search.MLAnaysisModeExpansionTest.class, org.alfresco.repo.search.MLAnaysisModeExpansionTest.class,
org.alfresco.repo.search.DocumentNavigatorTest.class, org.alfresco.repo.search.DocumentNavigatorTest.class,
org.alfresco.util.NumericEncodingTest.class, org.alfresco.util.NumericEncodingTest.class,
org.alfresco.repo.search.impl.parsers.CMIS_FTSTest.class, org.alfresco.repo.search.impl.parsers.CMIS_FTSTest.class,
org.alfresco.repo.search.impl.parsers.CMISTest.class, org.alfresco.repo.search.impl.parsers.CMISTest.class,
org.alfresco.repo.search.impl.parsers.FTSTest.class, org.alfresco.repo.search.impl.parsers.FTSTest.class,
org.alfresco.repo.security.authentication.AlfrescoSSLSocketFactoryTest.class, org.alfresco.repo.security.authentication.AlfrescoSSLSocketFactoryTest.class,
org.alfresco.repo.security.authentication.AuthorizationTest.class, org.alfresco.repo.security.authentication.AuthorizationTest.class,
org.alfresco.repo.security.permissions.PermissionCheckedCollectionTest.class, org.alfresco.repo.security.permissions.PermissionCheckedCollectionTest.class,
org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSetTest.class, org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSetTest.class,
org.alfresco.repo.security.permissions.impl.acegi.ACLEntryVoterUtilsTest.class, org.alfresco.repo.security.permissions.impl.acegi.ACLEntryVoterUtilsTest.class,
org.alfresco.repo.security.authentication.ChainingAuthenticationServiceTest.class, org.alfresco.repo.security.authentication.ChainingAuthenticationServiceTest.class,
org.alfresco.repo.security.authentication.NameBasedUserNameGeneratorTest.class, org.alfresco.repo.security.authentication.NameBasedUserNameGeneratorTest.class,
org.alfresco.repo.version.common.VersionImplTest.class, org.alfresco.repo.version.common.VersionImplTest.class,
org.alfresco.repo.version.common.VersionHistoryImplTest.class, org.alfresco.repo.version.common.VersionHistoryImplTest.class,
org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicyTest.class, org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicyTest.class,
org.alfresco.repo.workflow.activiti.WorklfowObjectFactoryTest.class, org.alfresco.repo.workflow.activiti.WorklfowObjectFactoryTest.class,
org.alfresco.repo.workflow.activiti.properties.ActivitiPriorityPropertyHandlerTest.class, org.alfresco.repo.workflow.activiti.properties.ActivitiPriorityPropertyHandlerTest.class,
org.alfresco.repo.workflow.WorkflowSuiteContextShutdownTest.class, org.alfresco.repo.workflow.WorkflowSuiteContextShutdownTest.class,
org.alfresco.repo.search.LuceneUtilsTest.class, org.alfresco.repo.search.LuceneUtilsTest.class,
org.alfresco.heartbeat.HBDataCollectorServiceImplTest.class, org.alfresco.heartbeat.HBDataCollectorServiceImplTest.class,
org.alfresco.heartbeat.jobs.LockingJobTest.class, org.alfresco.heartbeat.jobs.LockingJobTest.class,
org.alfresco.heartbeat.jobs.QuartzJobSchedulerTest.class, org.alfresco.heartbeat.jobs.QuartzJobSchedulerTest.class,
org.alfresco.heartbeat.AuthoritiesDataCollectorTest.class, org.alfresco.heartbeat.AuthoritiesDataCollectorTest.class,
org.alfresco.heartbeat.ConfigurationDataCollectorTest.class, org.alfresco.heartbeat.ConfigurationDataCollectorTest.class,
org.alfresco.heartbeat.InfoDataCollectorTest.class, org.alfresco.heartbeat.InfoDataCollectorTest.class,
org.alfresco.heartbeat.ModelUsageDataCollectorTest.class, org.alfresco.heartbeat.ModelUsageDataCollectorTest.class,
org.alfresco.heartbeat.SessionsUsageDataCollectorTest.class, org.alfresco.heartbeat.SessionsUsageDataCollectorTest.class,
org.alfresco.heartbeat.SystemUsageDataCollectorTest.class, org.alfresco.heartbeat.SystemUsageDataCollectorTest.class,
org.alfresco.util.BeanExtenderUnitTest.class, org.alfresco.util.BeanExtenderUnitTest.class,
org.alfresco.util.bean.HierarchicalBeanLoaderTest.class, org.alfresco.util.bean.HierarchicalBeanLoaderTest.class,
org.alfresco.util.resource.HierarchicalResourceLoaderTest.class, org.alfresco.util.resource.HierarchicalResourceLoaderTest.class,
org.alfresco.repo.events.ClientUtilTest.class, org.alfresco.repo.events.ClientUtilTest.class,
org.alfresco.repo.rendition2.RenditionService2Test.class, org.alfresco.repo.rendition2.RenditionService2Test.class,
org.alfresco.repo.rendition2.TransformationOptionsConverterTest.class, org.alfresco.repo.rendition2.TransformationOptionsConverterTest.class,
org.alfresco.repo.event2.RepoEvent2UnitSuite.class, org.alfresco.repo.event2.RepoEvent2UnitSuite.class,
org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class, org.alfresco.util.schemacomp.SchemaDifferenceHelperUnitTest.class,
org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class, org.alfresco.repo.tagging.TaggingServiceImplUnitTest.class,
org.alfresco.repo.serviceaccount.ServiceAccountRegistryImplTest.class org.alfresco.repo.serviceaccount.ServiceAccountRegistryImplTest.class
}) })
public class AllUnitTestsSuite public class AllUnitTestsSuite
{ {}
}

View File

@@ -1,164 +1,177 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.action.executer; package org.alfresco.repo.action.executer;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import org.alfresco.model.ContentModel; import org.junit.Before;
import org.alfresco.repo.action.ActionImpl; import org.junit.Test;
import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.springframework.extensions.surf.util.I18NUtil;
import org.alfresco.service.cmr.action.ActionDefinition; import org.springframework.transaction.annotation.Transactional;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.model.ContentModel;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.repo.action.ActionImpl;
import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.repo.action.access.ActionAccessRestriction;
import org.alfresco.service.namespace.QName; import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.util.BaseSpringTest; import org.alfresco.service.cmr.action.ActionDefinition;
import org.alfresco.util.GUID; import org.alfresco.service.cmr.action.ParameterDefinition;
import org.junit.Before; import org.alfresco.service.cmr.repository.NodeRef;
import org.junit.Test; import org.alfresco.service.cmr.repository.NodeService;
import org.springframework.extensions.surf.util.I18NUtil; import org.alfresco.service.cmr.repository.StoreRef;
import org.springframework.transaction.annotation.Transactional; import org.alfresco.service.namespace.QName;
import org.alfresco.util.BaseSpringTest;
/** import org.alfresco.util.GUID;
* Add features action execution test
* /**
* @author Roy Wetherall * Add features action execution test
*/ *
@Transactional * @author Roy Wetherall
public class AddFeaturesActionExecuterTest extends BaseSpringTest */
{ @Transactional
/** public class AddFeaturesActionExecuterTest extends BaseSpringTest
* The node service {
*/ /**
private NodeService nodeService; * Id used to identify the test action created
*/
/** private final static String ID = GUID.generate();
* The store reference /**
*/ * The node service
private StoreRef testStoreRef; */
private NodeService nodeService;
/** /**
* The root node reference * The store reference
*/ */
private NodeRef rootNodeRef; private StoreRef testStoreRef;
/**
/** * The root node reference
* The test node reference */
*/ private NodeRef rootNodeRef;
private NodeRef nodeRef; /**
* The test node reference
/** */
* The add features action executer private NodeRef nodeRef;
*/ /**
private AddFeaturesActionExecuter executer; * The add features action executer
*/
/** private AddFeaturesActionExecuter executer;
* Id used to identify the test action created
*/ /**
private final static String ID = GUID.generate(); * Called at the begining of all tests
*/
/** @Before
* Called at the begining of all tests public void before() throws Exception
*/ {
@Before this.nodeService = (NodeService) this.applicationContext.getBean("nodeService");
public void before() throws Exception
{ AuthenticationComponent authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent");
this.nodeService = (NodeService)this.applicationContext.getBean("nodeService"); authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName());
AuthenticationComponent authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); // Create the store and get the root node
authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); this.testStoreRef = this.nodeService.createStore(
StoreRef.PROTOCOL_WORKSPACE, "Test_"
// Create the store and get the root node + System.currentTimeMillis());
this.testStoreRef = this.nodeService.createStore( this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef);
StoreRef.PROTOCOL_WORKSPACE, "Test_"
+ System.currentTimeMillis()); // Create the node used for tests
this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef); this.nodeRef = this.nodeService.createNode(
this.rootNodeRef,
// Create the node used for tests ContentModel.ASSOC_CHILDREN,
this.nodeRef = this.nodeService.createNode( QName.createQName("{test}testnode"),
this.rootNodeRef, ContentModel.TYPE_CONTENT).getChildRef();
ContentModel.ASSOC_CHILDREN,
QName.createQName("{test}testnode"), // Get the executer instance
ContentModel.TYPE_CONTENT).getChildRef(); this.executer = (AddFeaturesActionExecuter) this.applicationContext.getBean(AddFeaturesActionExecuter.NAME);
}
// Get the executer instance
this.executer = (AddFeaturesActionExecuter)this.applicationContext.getBean(AddFeaturesActionExecuter.NAME); /**
} * Test execution
*/
/** @Test
* Test execution public void testExecution()
*/ {
@Test // Check that the node does not have the classifiable aspect
public void testExecution() assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE));
{
// Check that the node does not have the classifiable aspect // Execute the action
assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE)); ActionImpl action = new ActionImpl(null, ID, AddFeaturesActionExecuter.NAME, null);
action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE);
// Execute the action this.executer.execute(action, this.nodeRef);
ActionImpl action = new ActionImpl(null, ID, AddFeaturesActionExecuter.NAME, null);
action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE); // Check that the node now has the classifiable aspect applied
this.executer.execute(action, this.nodeRef); assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE));
}
// Check that the node now has the classifiable aspect applied
assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE)); /**
} * MNT-15802
*/
/** @Test
* MNT-15802 public void testCheckLocalizedParamDefintionWithConstraint()
*/ {
@Test // test for other than default locale
public void testCheckLocalizedParamDefintionWithConstraint() I18NUtil.setLocale(Locale.GERMAN);
{
// test for other than default locale ActionDefinition actionDef = executer.getActionDefinition();
I18NUtil.setLocale(Locale.GERMAN);
List<ParameterDefinition> paramDef = actionDef.getParameterDefinitions();
ActionDefinition actionDef = executer.getActionDefinition(); assertNotNull(paramDef);
List<ParameterDefinition> paramDef = actionDef.getParameterDefinitions(); String constraintName = paramDef.get(0).getParameterConstraintName();
assertNotNull(paramDef); assertNotNull(constraintName);
assertEquals(AddFeaturesActionExecuter.PARAM_CONSTRAINT, constraintName);
String constraintName = paramDef.get(0).getParameterConstraintName();
assertNotNull(constraintName); // test for other than default locale
assertEquals(AddFeaturesActionExecuter.PARAM_CONSTRAINT, constraintName); I18NUtil.setLocale(Locale.ITALY);
// test for other than default locale actionDef = executer.getActionDefinition();
I18NUtil.setLocale(Locale.ITALY);
paramDef = actionDef.getParameterDefinitions();
actionDef = executer.getActionDefinition(); assertNotNull(paramDef);
paramDef = actionDef.getParameterDefinitions(); constraintName = paramDef.get(0).getParameterConstraintName();
assertNotNull(paramDef); assertNotNull(constraintName);
assertEquals(AddFeaturesActionExecuter.PARAM_CONSTRAINT, constraintName);
constraintName = paramDef.get(0).getParameterConstraintName();
assertNotNull(constraintName); I18NUtil.setLocale(Locale.getDefault());
assertEquals(AddFeaturesActionExecuter.PARAM_CONSTRAINT, constraintName);
}
I18NUtil.setLocale(Locale.getDefault());
/**
} * Test check actionContext param is removed from adhoc properties
} */
@Test
public void testCheckActionContext()
{
// Execute the action
ActionImpl action = new ActionImpl(null, ID, AddFeaturesActionExecuter.NAME, null);
action.setParameterValue(ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME, ActionAccessRestriction.V1_ACTION_CONTEXT);
action.setParameterValue(AddFeaturesActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE);
this.executer.execute(action, this.nodeRef);
// Ensure the actionContext parameter has been removed
assertFalse(nodeService.getProperties(this.nodeRef).containsKey(QName.createQName(ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME)));
}
}

View File

@@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
@@ -25,16 +25,21 @@
*/ */
package org.alfresco.repo.rendition2; package org.alfresco.repo.rendition2;
import static org.alfresco.model.ContentModel.PROP_CONTENT;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.alfresco.model.ContentModel.PROP_CONTENT;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel; import org.alfresco.model.RenditionModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -44,8 +49,6 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.junit.BeforeClass;
import org.junit.Test;
/** /**
* Integration tests for {@link RenditionService2} * Integration tests for {@link RenditionService2}
@@ -62,37 +65,37 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
// PDF transformation // PDF transformation
@Test @Test
public void testLocalRenderPdfToJpegMedium() public void testLocalRenderPdfToJpegMedium()
{ {
checkRendition("quick.pdf", "medium", true); checkRendition("quick.pdf", "medium", true);
} }
@Test @Test
public void testLocalRenderPdfToDoclib() public void testLocalRenderPdfToDoclib()
{ {
checkRendition("quick.pdf", "doclib", true); checkRendition("quick.pdf", "doclib", true);
} }
@Test @Test
public void testLocalRenderPdfJpegImgpreview() public void testLocalRenderPdfJpegImgpreview()
{ {
checkRendition("quick.pdf", "imgpreview", true); checkRendition("quick.pdf", "imgpreview", true);
} }
@Test @Test
public void testLocalRenderPdfPngAvatar() public void testLocalRenderPdfPngAvatar()
{ {
checkRendition("quick.pdf", "avatar", true); checkRendition("quick.pdf", "avatar", true);
} }
@Test @Test
public void testLocalRenderPdfPngAvatar32() public void testLocalRenderPdfPngAvatar32()
{ {
checkRendition("quick.pdf", "avatar32", true); checkRendition("quick.pdf", "avatar32", true);
} }
@Test @Test
public void testLocalRenderPdfFlashWebpreview() public void testLocalRenderPdfFlashWebpreview()
{ {
checkRendition("quick.pdf", "webpreview", false); checkRendition("quick.pdf", "webpreview", false);
} }
@@ -100,43 +103,43 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
// DOCX transformation // DOCX transformation
@Test @Test
public void testLocalRenderDocxJpegMedium() public void testLocalRenderDocxJpegMedium()
{ {
checkRendition("quick.docx", "medium", true); checkRendition("quick.docx", "medium", true);
} }
@Test @Test
public void testLocalRenderDocxDoclib() public void testLocalRenderDocxDoclib()
{ {
checkRendition("quick.docx", "doclib", true); checkRendition("quick.docx", "doclib", true);
} }
@Test @Test
public void testLocalRenderDocxJpegImgpreview() public void testLocalRenderDocxJpegImgpreview()
{ {
checkRendition("quick.docx", "imgpreview", true); checkRendition("quick.docx", "imgpreview", true);
} }
@Test @Test
public void testLocalRenderDocxPngAvatar() public void testLocalRenderDocxPngAvatar()
{ {
checkRendition("quick.docx", "avatar", true); checkRendition("quick.docx", "avatar", true);
} }
@Test @Test
public void testLocalRenderDocxPngAvatar32() public void testLocalRenderDocxPngAvatar32()
{ {
checkRendition("quick.docx", "avatar32", true); checkRendition("quick.docx", "avatar32", true);
} }
@Test @Test
public void testLocalRenderDocxFlashWebpreview() public void testLocalRenderDocxFlashWebpreview()
{ {
checkRendition("quick.docx", "webpreview", false); checkRendition("quick.docx", "webpreview", false);
} }
@Test @Test
public void testLocalRenderDocxPdf() public void testLocalRenderDocxPdf()
{ {
checkRendition("quick.docx", "pdf", true); checkRendition("quick.docx", "pdf", true);
} }
@@ -150,7 +153,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void changedSourceToNullContent() public void changedSourceToNullContent()
{ {
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
render(ADMIN, sourceNodeRef, DOC_LIB); render(ADMIN, sourceNodeRef, DOC_LIB);
@@ -158,8 +161,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
clearContent(ADMIN, sourceNodeRef); clearContent(ADMIN, sourceNodeRef);
render(ADMIN, sourceNodeRef, DOC_LIB); render(ADMIN, sourceNodeRef, DOC_LIB);
ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN);
renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN);
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false);
assertNull("There should be no rendition as there was no content", assoc); assertNull("There should be no rendition as there was no content", assoc);
} }
@@ -190,8 +192,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
clearContent(ADMIN, sourceNodeRef); clearContent(ADMIN, sourceNodeRef);
render(ADMIN, sourceNodeRef, DOC_LIB); render(ADMIN, sourceNodeRef, DOC_LIB);
ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> ChildAssociationRef assoc = AuthenticationUtil.runAs(() -> renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN);
renditionService2.getRenditionByName(sourceNodeRef, DOC_LIB), ADMIN);
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, false);
assertNull("There should be no rendition as there was no content", assoc); assertNull("There should be no rendition as there was no content", assoc);
@@ -201,7 +202,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void testCreateRenditionByUser() public void testCreateRenditionByUser()
{ {
String userName = createRandomUser(); String userName = createRandomUser();
NodeRef sourceNodeRef = createSource(userName, "quick.jpg"); NodeRef sourceNodeRef = createSource(userName, "quick.jpg");
@@ -211,13 +212,12 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void testReadRenditionByOtherUser() public void testReadRenditionByOtherUser()
{ {
String ownerUserName = createRandomUser(); String ownerUserName = createRandomUser();
NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg");
String otherUserName = createRandomUser(); String otherUserName = createRandomUser();
transactionService.getRetryingTransactionHelper().doInTransaction(() -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
{
permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true); permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true);
return null; return null;
}); });
@@ -231,13 +231,12 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void testRenderByReader() public void testRenderByReader()
{ {
String ownerUserName = createRandomUser(); String ownerUserName = createRandomUser();
NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg");
String otherUserName = createRandomUser(); String otherUserName = createRandomUser();
transactionService.getRetryingTransactionHelper().doInTransaction(() -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
{
permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true); permissionService.setPermission(sourceNodeRef, otherUserName, PermissionService.READ, true);
return null; return null;
}); });
@@ -251,14 +250,13 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
} }
@Test @Test
public void testAccessWithNoPermissions() public void testAccessWithNoPermissions()
{ {
String ownerUserName = createRandomUser(); String ownerUserName = createRandomUser();
NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg");
render(ownerUserName, sourceNodeRef, DOC_LIB); render(ownerUserName, sourceNodeRef, DOC_LIB);
String noPermissionsUser = createRandomUser(); String noPermissionsUser = createRandomUser();
transactionService.getRetryingTransactionHelper().doInTransaction(() -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
{
permissionService.setPermission(sourceNodeRef, noPermissionsUser, PermissionService.ALL_PERMISSIONS, false); permissionService.setPermission(sourceNodeRef, noPermissionsUser, PermissionService.ALL_PERMISSIONS, false);
return null; return null;
}); });
@@ -280,12 +278,9 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg"); NodeRef sourceNodeRef = createSource(ownerUserName, "quick.jpg");
final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ownerUserName));
AuthenticationUtil.runAs(() ->
renditionService.render(sourceNodeRef, doclibRendDefQName), ownerUserName));
NodeRef oldRendition = AuthenticationUtil.runAs(() -> NodeRef oldRendition = AuthenticationUtil.runAs(() -> renditionService.getRenditionByName(sourceNodeRef, doclibRendDefQName).getChildRef(), ownerUserName);
renditionService.getRenditionByName(sourceNodeRef, doclibRendDefQName).getChildRef(), ownerUserName);
assertFalse("The rendition should be generated by old Rendition Service", assertFalse("The rendition should be generated by old Rendition Service",
AuthenticationUtil.runAs(() -> nodeService.hasAspect(oldRendition, RenditionModel.ASPECT_RENDITION2), ownerUserName)); AuthenticationUtil.runAs(() -> nodeService.hasAspect(oldRendition, RenditionModel.ASPECT_RENDITION2), ownerUserName));
@@ -335,12 +330,10 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
renditionService2.setEnabled(false); renditionService2.setEnabled(false);
// Call 'clearRenditionContentData' method directly to prove rendition content will be cleaned // Call 'clearRenditionContentData' method directly to prove rendition content will be cleaned
AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> AuthenticationUtil.runAs((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> renditionService2.clearRenditionContentData(sourceNodeRef, DOC_LIB);
{ return null;
renditionService2.clearRenditionContentData(sourceNodeRef, DOC_LIB); }), ADMIN);
return null;
}), ADMIN);
// The rendition should not have content by now // The rendition should not have content by now
assertNull("Rendition has content", nodeService.getProperty(renditionNodeRef, ContentModel.PROP_CONTENT)); assertNull("Rendition has content", nodeService.getProperty(renditionNodeRef, ContentModel.PROP_CONTENT));
@@ -356,8 +349,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
/** /**
* Tests if a rendition without content (but with contentHashCode) can be generated again. * Tests if a rendition without content (but with contentHashCode) can be generated again.
* <p> * <p>
* If the rendition consumption receives a null InputStream, the contentHashCode should be cleaned from the * If the rendition consumption receives a null InputStream, the contentHashCode should be cleaned from the rendition node, allowing new requests to generate the rendition.
* rendition node, allowing new requests to generate the rendition.
* </p> * </p>
*/ */
@Test @Test
@@ -369,8 +361,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
/** /**
* Tests if a rendition without content (but with contentHashCode) can be generated again. * Tests if a rendition without content (but with contentHashCode) can be generated again.
* <p> * <p>
* If the rendition consumption receives a zero length InputStream, the contentHashCode should be cleaned from the * If the rendition consumption receives a zero length InputStream, the contentHashCode should be cleaned from the rendition node, allowing new requests to generate the rendition.
* rendition node, allowing new requests to generate the rendition.
* </p> * </p>
*/ */
@Test @Test
@@ -492,22 +483,16 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
AuthenticationUtil.runAs(() ->
renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
assertNotNull("The old renditions service did not render", waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true)); assertNotNull("The old renditions service did not render", waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true));
List<String> lastThumbnailModification = transactionService.getRetryingTransactionHelper() List<String> lastThumbnailModification = transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN));
AuthenticationUtil.runAs(() ->
(List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN));
updateContent(ADMIN, sourceNodeRef, "quick.png"); updateContent(ADMIN, sourceNodeRef, "quick.png");
List<String> newThumbnailModification = null; List<String> newThumbnailModification = null;
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
{ {
newThumbnailModification = transactionService.getRetryingTransactionHelper() newThumbnailModification = transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN));
AuthenticationUtil.runAs(() ->
(List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA), ADMIN));
if (!newThumbnailModification.equals(lastThumbnailModification)) if (!newThumbnailModification.equals(lastThumbnailModification))
{ {
break; break;
@@ -579,9 +564,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
AuthenticationUtil.runAs(() ->
renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true);
renditionService2.setEnabled(true); renditionService2.setEnabled(true);
@@ -652,9 +635,7 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg"); NodeRef sourceNodeRef = createSource(ADMIN, "quick.jpg");
final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); final QName doclibRendDefQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib");
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction(() -> .doInTransaction(() -> AuthenticationUtil.runAs(() -> renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
AuthenticationUtil.runAs(() ->
renditionService.render(sourceNodeRef, doclibRendDefQName), ADMIN));
waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true); waitForRendition(ADMIN, sourceNodeRef, DOC_LIB, true);
renditionService2.setEnabled(true); renditionService2.setEnabled(true);
@@ -682,4 +663,57 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
renditionService2.setEnabled(true); renditionService2.setEnabled(true);
} }
} }
@Test
public void testTextExtractTransformAllowedWhenThumbnailDisabled()
{
// create a source node
NodeRef sourceNodeRef = createSource(ADMIN, "quick.pdf");
assertNotNull("Node not generated", sourceNodeRef);
String replyQueue = "org.test.queue";
String targetMimetype = MimetypeMap.MIMETYPE_TEXT_PLAIN;
TransformDefinition textExtractTransform = new TransformDefinition(
targetMimetype,
java.util.Collections.emptyMap(),
"clientData",
replyQueue,
"requestId");
renditionService2.setThumbnailsEnabled(false);
try
{
// Should NOT throw, as this is a text extract transform
AuthenticationUtil.runAs(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
renditionService2.transform(sourceNodeRef, textExtractTransform);
return null;
});
return null;
}, ADMIN);
}
finally
{
renditionService2.setThumbnailsEnabled(true);
}
}
@Test
public void testMetadataExtractTransformAllowedWhenThumbnailDisabled()
{
// create a source node
NodeRef sourceNodeRef = createSource(ADMIN, "quick.pdf");
assertNotNull("Node not generated", sourceNodeRef);
renditionService2.setThumbnailsEnabled(false);
try
{
// Should NOT throw, as this is a metadata extract transform
extract(ADMIN, sourceNodeRef);
waitForExtract(ADMIN, sourceNodeRef, true);
}
finally
{
renditionService2.setThumbnailsEnabled(true);
}
}
} }

View File

@@ -38,8 +38,7 @@ import java.util.Set;
import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.Scope; import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import net.minidev.json.JSONObject;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.ClientRegistrationProvider;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@@ -51,12 +50,17 @@ import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.ClientRegistrationProvider;
public class ClientRegistrationProviderUnitTest public class ClientRegistrationProviderUnitTest
{ {
private static final String CLIENT_ID = "alfresco"; private static final String CLIENT_ID = "alfresco";
private static final String OPENID_CONFIGURATION = "{\"token_endpoint\":\"https://login.serviceonline.alfresco/common/oauth2/v2.0/token\",\"token_endpoint_auth_methods_supported\":[\"client_secret_post\",\"private_key_jwt\",\"client_secret_basic\"],\"jwks_uri\":\"https://login.serviceonline.alfresco/common/discovery/v2.0/keys\",\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"subject_types_supported\":[\"pairwise\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"response_types_supported\":[\"code\",\"id_token\",\"code id_token\",\"id_token token\"],\"scopes_supported\":[\"openid\",\"profile\",\"email\",\"offline_access\"],\"issuer\":\"https://login.serviceonline.alfresco/alfresco/v2.0\",\"request_uri_parameter_supported\":false,\"userinfo_endpoint\":\"https://graph.service.alfresco/oidc/userinfo\",\"authorization_endpoint\":\"https://login.serviceonline.alfresco/common/oauth2/v2.0/authorize\",\"device_authorization_endpoint\":\"https://login.serviceonline.alfresco/common/oauth2/v2.0/devicecode\",\"http_logout_supported\":true,\"frontchannel_logout_supported\":true,\"end_session_endpoint\":\"https://login.serviceonline.alfresco/common/oauth2/v2.0/logout\",\"claims_supported\":[\"sub\",\"iss\",\"cloud_instance_name\",\"cloud_instance_host_name\",\"cloud_graph_host_name\",\"msgraph_host\",\"aud\",\"exp\",\"iat\",\"auth_time\",\"acr\",\"nonce\",\"preferred_username\",\"name\",\"tid\",\"ver\",\"at_hash\",\"c_hash\",\"email\"],\"kerberos_endpoint\":\"https://login.serviceonline.alfresco/common/kerberos\",\"tenant_region_scope\":null,\"cloud_instance_name\":\"serviceonline.alfresco\",\"cloud_graph_host_name\":\"graph.oidc.net\",\"msgraph_host\":\"graph.service.alfresco\",\"rbac_url\":\"https://pas.oidc.alfresco\"}"; private static final String OPENID_CONFIGURATION = "{\"token_endpoint\":\"https://login.serviceonline.alfresco/common/oauth2/v2.0/token\",\"token_endpoint_auth_methods_supported\":[\"client_secret_post\",\"private_key_jwt\",\"client_secret_basic\"],\"jwks_uri\":\"https://login.serviceonline.alfresco/common/discovery/v2.0/keys\",\"response_modes_supported\":[\"query\",\"fragment\",\"form_post\"],\"subject_types_supported\":[\"pairwise\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"response_types_supported\":[\"code\",\"id_token\",\"code id_token\",\"id_token token\"],\"scopes_supported\":[\"openid\",\"profile\",\"email\",\"offline_access\"],\"issuer\":\"https://login.serviceonline.alfresco/alfresco/v2.0\",\"request_uri_parameter_supported\":false,\"userinfo_endpoint\":\"https://graph.service.alfresco/oidc/userinfo\",\"authorization_endpoint\":\"https://login.serviceonline.alfresco/common/oauth2/v2.0/authorize\",\"device_authorization_endpoint\":\"https://login.serviceonline.alfresco/common/oauth2/v2.0/devicecode\",\"http_logout_supported\":true,\"frontchannel_logout_supported\":true,\"end_session_endpoint\":\"https://login.serviceonline.alfresco/common/oauth2/v2.0/logout\",\"claims_supported\":[\"sub\",\"iss\",\"cloud_instance_name\",\"cloud_instance_host_name\",\"cloud_graph_host_name\",\"msgraph_host\",\"aud\",\"exp\",\"iat\",\"auth_time\",\"acr\",\"nonce\",\"preferred_username\",\"name\",\"tid\",\"ver\",\"at_hash\",\"c_hash\",\"email\"],\"kerberos_endpoint\":\"https://login.serviceonline.alfresco/common/kerberos\",\"tenant_region_scope\":null,\"cloud_instance_name\":\"serviceonline.alfresco\",\"cloud_graph_host_name\":\"graph.oidc.net\",\"msgraph_host\":\"graph.service.alfresco\",\"rbac_url\":\"https://pas.oidc.alfresco\"}";
private static final String DISCOVERY_PATH_SEGMENTS = "/.well-known/openid-configuration"; private static final String DISCOVERY_PATH_SEGMENTS = "/.well-known/openid-configuration";
private static final String AUTH_SERVER = "https://login.serviceonline.alfresco"; private static final String AUTH_SERVER = "https://login.serviceonline.alfresco";
private static final String ADMIN_CONSOLE_SCOPES = "openid,email,profile,offline_access";
private static final String PSSWD_GRANT_SCOPES = "openid,email,profile";
private static final String ISSUER_ATRR = "issuer";
private IdentityServiceConfig config; private IdentityServiceConfig config;
private RestTemplate restTemplate; private RestTemplate restTemplate;
@@ -70,6 +74,9 @@ public class ClientRegistrationProviderUnitTest
config = new IdentityServiceConfig(); config = new IdentityServiceConfig();
config.setAuthServerUrl(AUTH_SERVER); config.setAuthServerUrl(AUTH_SERVER);
config.setResource(CLIENT_ID); config.setResource(CLIENT_ID);
config.setAdminConsoleScopes(ADMIN_CONSOLE_SCOPES);
config.setPasswordGrantScopes(PSSWD_GRANT_SCOPES);
config.setIssuerAttribute(ISSUER_ATRR);
restTemplate = mock(RestTemplate.class); restTemplate = mock(RestTemplate.class);
ResponseEntity responseEntity = mock(ResponseEntity.class); ResponseEntity responseEntity = mock(ResponseEntity.class);
@@ -90,7 +97,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration( ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration(
restTemplate); restTemplate);
assertThat(clientRegistration).isNotNull(); assertThat(clientRegistration).isNotNull();
assertThat(clientRegistration.getClientId()).isNotNull(); assertThat(clientRegistration.getClientId()).isNotNull();
assertThat(clientRegistration.getProviderDetails().getAuthorizationUri()).isNotNull(); assertThat(clientRegistration.getProviderDetails().getAuthorizationUri()).isNotNull();
@@ -99,7 +106,7 @@ public class ClientRegistrationProviderUnitTest
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint()).isNotNull(); assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint()).isNotNull();
assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isNotNull(); assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isNotNull();
assertThat(requestEntityCaptor.getValue().getUrl().toASCIIString()).isEqualTo( assertThat(requestEntityCaptor.getValue().getUrl().toASCIIString()).isEqualTo(
AUTH_SERVER + DISCOVERY_PATH_SEGMENTS); AUTH_SERVER + DISCOVERY_PATH_SEGMENTS);
} }
} }
@@ -112,7 +119,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration( ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration(
restTemplate); restTemplate);
assertThat(clientRegistration).isNotNull(); assertThat(clientRegistration).isNotNull();
assertThat(clientRegistration.getClientId()).isNotNull(); assertThat(clientRegistration.getClientId()).isNotNull();
assertThat(clientRegistration.getProviderDetails().getAuthorizationUri()).isNotNull(); assertThat(clientRegistration.getProviderDetails().getAuthorizationUri()).isNotNull();
@@ -121,7 +128,7 @@ public class ClientRegistrationProviderUnitTest
assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint()).isNotNull(); assertThat(clientRegistration.getProviderDetails().getUserInfoEndpoint()).isNotNull();
assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isNotNull(); assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isNotNull();
assertThat(requestEntityCaptor.getValue().getUrl().toASCIIString()).isEqualTo( assertThat(requestEntityCaptor.getValue().getUrl().toASCIIString()).isEqualTo(
AUTH_SERVER + DISCOVERY_PATH_SEGMENTS); AUTH_SERVER + DISCOVERY_PATH_SEGMENTS);
} }
} }
@@ -134,7 +141,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
assertThrows(IdentityServiceException.class, assertThrows(IdentityServiceException.class,
() -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate)); () -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate));
} }
} }
@@ -148,7 +155,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
assertThrows(IdentityServiceException.class, assertThrows(IdentityServiceException.class,
() -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate)); () -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate));
} }
} }
@@ -161,7 +168,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
assertThrows(IdentityServiceException.class, assertThrows(IdentityServiceException.class,
() -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate)); () -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate));
} }
} }
@@ -174,7 +181,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
assertThrows(IdentityServiceException.class, assertThrows(IdentityServiceException.class,
() -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate)); () -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate));
} }
} }
@@ -187,7 +194,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
assertThrows(IdentityServiceException.class, assertThrows(IdentityServiceException.class,
() -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate)); () -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate));
} }
} }
@@ -200,7 +207,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
assertThrows(IdentityServiceException.class, assertThrows(IdentityServiceException.class,
() -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate)); () -> new ClientRegistrationProvider(config).createClientRegistration(restTemplate));
} }
} }
@@ -215,7 +222,7 @@ public class ClientRegistrationProviderUnitTest
new ClientRegistrationProvider(config).createClientRegistration(restTemplate); new ClientRegistrationProvider(config).createClientRegistration(restTemplate);
assertThat(requestEntityCaptor.getValue().getUrl().toASCIIString()).isEqualTo( assertThat(requestEntityCaptor.getValue().getUrl().toASCIIString()).isEqualTo(
AUTH_SERVER + "/realms/alfresco" + DISCOVERY_PATH_SEGMENTS); AUTH_SERVER + "/realms/alfresco" + DISCOVERY_PATH_SEGMENTS);
} }
} }
@@ -227,10 +234,10 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration( ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration(
restTemplate); restTemplate);
assertThat( assertThat(
clientRegistration.getScopes().containsAll( clientRegistration.getScopes().containsAll(
Set.of("openid", "profile", "email"))).isTrue(); Set.of("openid", "profile", "email"))).isTrue();
} }
} }
@@ -243,7 +250,7 @@ public class ClientRegistrationProviderUnitTest
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse); providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration( ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration(
restTemplate); restTemplate);
assertThat(clientRegistration.getScopes().size()).isEqualTo(1); assertThat(clientRegistration.getScopes().size()).isEqualTo(1);
assertThat(clientRegistration.getScopes().stream().findFirst().get()).isEqualTo("openid"); assertThat(clientRegistration.getScopes().stream().findFirst().get()).isEqualTo("openid");
} }
@@ -260,7 +267,45 @@ public class ClientRegistrationProviderUnitTest
new ClientRegistrationProvider(config).createClientRegistration(restTemplate); new ClientRegistrationProvider(config).createClientRegistration(restTemplate);
assertThat(requestEntityCaptor.getValue().getUrl().toASCIIString()).isEqualTo( assertThat(requestEntityCaptor.getValue().getUrl().toASCIIString()).isEqualTo(
"https://login.serviceonline.alfresco/alfresco/v2.0" + DISCOVERY_PATH_SEGMENTS); "https://login.serviceonline.alfresco/alfresco/v2.0" + DISCOVERY_PATH_SEGMENTS);
} }
} }
}
@Test
public void shouldUseDefaultIssuerAttribute()
{
config.setIssuerUrl(null);
try (MockedStatic<OIDCProviderMetadata> providerMetadata = Mockito.mockStatic(OIDCProviderMetadata.class))
{
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration(
restTemplate);
assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isEqualTo("https://login.serviceonline.alfresco/alfresco/v2.0");
}
}
@Test
public void shouldUseCustomIssuerAttribute()
{
try (MockedStatic<OIDCProviderMetadata> providerMetadata = Mockito.mockStatic(OIDCProviderMetadata.class))
{
config.setIssuerAttribute("access_token_issuer");
when(oidcResponse.getCustomParameters()).thenReturn(createJSONObject("access_token_issuer", "https://login.serviceonline.alfresco/alfresco/v2.0/at_trust"));
providerMetadata.when(() -> OIDCProviderMetadata.parse(any(String.class))).thenReturn(oidcResponse);
ClientRegistration clientRegistration = new ClientRegistrationProvider(config).createClientRegistration(
restTemplate);
assertThat(clientRegistration.getProviderDetails().getIssuerUri()).isEqualTo("https://login.serviceonline.alfresco/alfresco/v2.0/at_trust");
}
}
private static JSONObject createJSONObject(String fieldName, String fieldValue)
{
JSONObject jsonObject = new JSONObject();
jsonObject.appendField(fieldName, fieldValue);
return jsonObject;
}
}

View File

@@ -1,172 +1,173 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2023 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.security.authentication.identityservice; package org.alfresco.repo.security.authentication.identityservice;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import java.net.ConnectException; import java.net.ConnectException;
import java.util.Optional; import java.util.Optional;
import org.alfresco.error.ExceptionStackUtil; import org.junit.After;
import org.alfresco.repo.security.authentication.AuthenticationContext; import org.junit.Before;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.junit.Test;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization; import org.springframework.beans.factory.annotation.Autowired;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant; import org.alfresco.error.ExceptionStackUtil;
import org.alfresco.repo.security.sync.UserRegistrySynchronizer; import org.alfresco.repo.security.authentication.AuthenticationContext;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
import org.alfresco.util.BaseSpringTest; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
import org.junit.After; import org.alfresco.repo.security.authentication.identityservice.user.OIDCUserInfo;
import org.junit.Before; import org.alfresco.repo.security.sync.UserRegistrySynchronizer;
import org.junit.Test; import org.alfresco.service.cmr.repository.NodeService;
import org.springframework.beans.factory.annotation.Autowired; import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService;
public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest import org.alfresco.util.BaseSpringTest;
{
private final IdentityServiceAuthenticationComponent authComponent = new IdentityServiceAuthenticationComponent(); public class IdentityServiceAuthenticationComponentTest extends BaseSpringTest
{
@Autowired private final IdentityServiceAuthenticationComponent authComponent = new IdentityServiceAuthenticationComponent();
private AuthenticationContext authenticationContext;
@Autowired
@Autowired private AuthenticationContext authenticationContext;
private TransactionService transactionService;
@Autowired
@Autowired private TransactionService transactionService;
private UserRegistrySynchronizer userRegistrySynchronizer;
@Autowired
@Autowired private UserRegistrySynchronizer userRegistrySynchronizer;
private NodeService nodeService;
@Autowired
@Autowired private NodeService nodeService;
private PersonService personService;
@Autowired
private PersonService personService;
private IdentityServiceJITProvisioningHandler jitProvisioning;
private IdentityServiceFacade mockIdentityServiceFacade; private IdentityServiceJITProvisioningHandler jitProvisioning;
private IdentityServiceFacade mockIdentityServiceFacade;
@Before
public void setUp() @Before
{ public void setUp()
authComponent.setAuthenticationContext(authenticationContext); {
authComponent.setTransactionService(transactionService); authComponent.setAuthenticationContext(authenticationContext);
authComponent.setUserRegistrySynchronizer(userRegistrySynchronizer); authComponent.setTransactionService(transactionService);
authComponent.setNodeService(nodeService); authComponent.setUserRegistrySynchronizer(userRegistrySynchronizer);
authComponent.setPersonService(personService); authComponent.setNodeService(nodeService);
authComponent.setPersonService(personService);
jitProvisioning = mock(IdentityServiceJITProvisioningHandler.class);
mockIdentityServiceFacade = mock(IdentityServiceFacade.class); jitProvisioning = mock(IdentityServiceJITProvisioningHandler.class);
authComponent.setJitProvisioningHandler(jitProvisioning); mockIdentityServiceFacade = mock(IdentityServiceFacade.class);
authComponent.setIdentityServiceFacade(mockIdentityServiceFacade); authComponent.setJitProvisioningHandler(jitProvisioning);
} authComponent.setIdentityServiceFacade(mockIdentityServiceFacade);
}
@After
public void tearDown() @After
{ public void tearDown()
authenticationContext.clearCurrentSecurityContext(); {
} authenticationContext.clearCurrentSecurityContext();
}
@Test (expected=AuthenticationException.class)
public void testAuthenticationFail() @Test(expected = AuthenticationException.class)
{ public void testAuthenticationFail()
final AuthorizationGrant grant = AuthorizationGrant.password("username", "password"); {
final AuthorizationGrant grant = AuthorizationGrant.password("username", "password");
doThrow(new AuthorizationException("Failed")).when(mockIdentityServiceFacade).authorize(grant);
doThrow(new AuthorizationException("Failed")).when(mockIdentityServiceFacade).authorize(grant);
authComponent.authenticateImpl("username", "password".toCharArray());
} authComponent.authenticateImpl("username", "password".toCharArray());
}
@Test(expected = AuthenticationException.class)
public void testAuthenticationFail_connectionException() @Test(expected = AuthenticationException.class)
{ public void testAuthenticationFail_connectionException()
final AuthorizationGrant grant = AuthorizationGrant.password("username", "password"); {
final AuthorizationGrant grant = AuthorizationGrant.password("username", "password");
doThrow(new AuthorizationException("Couldn't connect to server", new ConnectException("ConnectionRefused")))
.when(mockIdentityServiceFacade).authorize(grant); doThrow(new AuthorizationException("Couldn't connect to server", new ConnectException("ConnectionRefused")))
.when(mockIdentityServiceFacade).authorize(grant);
try
{ try
authComponent.authenticateImpl("username", "password".toCharArray()); {
} authComponent.authenticateImpl("username", "password".toCharArray());
catch (RuntimeException ex) }
{ catch (RuntimeException ex)
Throwable cause = ExceptionStackUtil.getCause(ex, ConnectException.class); {
assertNotNull(cause); Throwable cause = ExceptionStackUtil.getCause(ex, ConnectException.class);
throw ex; assertNotNull(cause);
} throw ex;
} }
}
@Test (expected=AuthenticationException.class)
public void testAuthenticationFail_otherException() @Test(expected = AuthenticationException.class)
{ public void testAuthenticationFail_otherException()
final AuthorizationGrant grant = AuthorizationGrant.password("username", "password"); {
final AuthorizationGrant grant = AuthorizationGrant.password("username", "password");
doThrow(new RuntimeException("Some other errors!"))
.when(mockIdentityServiceFacade) doThrow(new RuntimeException("Some other errors!"))
.authorize(grant); .when(mockIdentityServiceFacade)
.authorize(grant);
authComponent.authenticateImpl("username", "password".toCharArray());
} authComponent.authenticateImpl("username", "password".toCharArray());
}
@Test
public void testAuthenticationPass() @Test
{ public void testAuthenticationPass()
final AuthorizationGrant grant = AuthorizationGrant.password("username", "password"); {
AccessTokenAuthorization authorization = mock(AccessTokenAuthorization.class); final AuthorizationGrant grant = AuthorizationGrant.password("username", "password");
IdentityServiceFacade.AccessToken accessToken = mock(IdentityServiceFacade.AccessToken.class); AccessTokenAuthorization authorization = mock(AccessTokenAuthorization.class);
IdentityServiceFacade.AccessToken accessToken = mock(IdentityServiceFacade.AccessToken.class);
when(authorization.getAccessToken()).thenReturn(accessToken);
when(accessToken.getTokenValue()).thenReturn("JWT_TOKEN"); when(authorization.getAccessToken()).thenReturn(accessToken);
when(mockIdentityServiceFacade.authorize(grant)).thenReturn(authorization); when(accessToken.getTokenValue()).thenReturn("JWT_TOKEN");
when(jitProvisioning.extractUserInfoAndCreateUserIfNeeded("JWT_TOKEN")) when(mockIdentityServiceFacade.authorize(grant)).thenReturn(authorization);
.thenReturn(Optional.of(new OIDCUserInfo("username", "", "", ""))); when(jitProvisioning.extractUserInfoAndCreateUserIfNeeded("JWT_TOKEN"))
.thenReturn(Optional.of(new OIDCUserInfo("username", "", "", "")));
authComponent.authenticateImpl("username", "password".toCharArray());
authComponent.authenticateImpl("username", "password".toCharArray());
// Check that the authenticated user has been set
assertEquals("User has not been set as expected.","username", authenticationContext.getCurrentUserName()); // Check that the authenticated user has been set
} assertEquals("User has not been set as expected.", "username", authenticationContext.getCurrentUserName());
}
@Test (expected= AuthenticationException.class)
public void testFallthroughWhenIdentityServiceFacadeIsNull() @Test(expected = AuthenticationException.class)
{ public void testFallthroughWhenIdentityServiceFacadeIsNull()
authComponent.setIdentityServiceFacade(null); {
authComponent.authenticateImpl("username", "password".toCharArray()); authComponent.setIdentityServiceFacade(null);
} authComponent.authenticateImpl("username", "password".toCharArray());
}
@Test
public void testSettingAllowGuestUser() @Test
{ public void testSettingAllowGuestUser()
authComponent.setAllowGuestLogin(true); {
assertTrue(authComponent.guestUserAuthenticationAllowed()); authComponent.setAllowGuestLogin(true);
assertTrue(authComponent.guestUserAuthenticationAllowed());
authComponent.setAllowGuestLogin(false);
assertFalse(authComponent.guestUserAuthenticationAllowed()); authComponent.setAllowGuestLogin(false);
} assertFalse(authComponent.guestUserAuthenticationAllowed());
} }
}

View File

@@ -25,29 +25,29 @@
*/ */
package org.alfresco.repo.security.authentication.identityservice; package org.alfresco.repo.security.authentication.identityservice;
import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Optional; import java.util.Optional;
import com.nimbusds.openid.connect.sdk.claims.PersonClaims; import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory;
import org.alfresco.repo.management.subsystems.DefaultChildApplicationContextManager; import org.alfresco.repo.management.subsystems.DefaultChildApplicationContextManager;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.identityservice.user.OIDCUserInfo;
import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.BaseSpringTest; import org.alfresco.util.BaseSpringTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@SuppressWarnings("PMD.AvoidAccessibilityAlteration") @SuppressWarnings("PMD.AvoidAccessibilityAlteration")
public class IdentityServiceJITProvisioningHandlerTest extends BaseSpringTest public class IdentityServiceJITProvisioningHandlerTest extends BaseSpringTest
@@ -61,12 +61,12 @@ public class IdentityServiceJITProvisioningHandlerTest extends BaseSpringTest
private IdentityServiceJITProvisioningHandler jitProvisioningHandler; private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
private final boolean isAuth0Enabled = Optional.ofNullable(System.getProperty("auth0.enabled")) private final boolean isAuth0Enabled = Optional.ofNullable(System.getProperty("auth0.enabled"))
.map(Boolean::valueOf) .map(Boolean::valueOf)
.orElse(false); .orElse(false);
private final String userPassword = Optional.ofNullable(System.getProperty("admin.password")) private final String userPassword = Optional.ofNullable(System.getProperty("admin.password"))
.filter(password -> isAuth0Enabled) .filter(password -> isAuth0Enabled)
.orElse("password"); .orElse("password");
@Before @Before
public void setup() public void setup()
@@ -75,16 +75,16 @@ public class IdentityServiceJITProvisioningHandlerTest extends BaseSpringTest
nodeService = (NodeService) applicationContext.getBean("nodeService"); nodeService = (NodeService) applicationContext.getBean("nodeService");
transactionService = (TransactionService) applicationContext.getBean("transactionService"); transactionService = (TransactionService) applicationContext.getBean("transactionService");
DefaultChildApplicationContextManager childApplicationContextManager = (DefaultChildApplicationContextManager) applicationContext DefaultChildApplicationContextManager childApplicationContextManager = (DefaultChildApplicationContextManager) applicationContext
.getBean("Authentication"); .getBean("Authentication");
ChildApplicationContextFactory childApplicationContextFactory = childApplicationContextManager.getChildApplicationContextFactory( ChildApplicationContextFactory childApplicationContextFactory = childApplicationContextManager.getChildApplicationContextFactory(
"identity-service1"); "identity-service1");
identityServiceFacade = (IdentityServiceFacade) childApplicationContextFactory.getApplicationContext() identityServiceFacade = (IdentityServiceFacade) childApplicationContextFactory.getApplicationContext()
.getBean("identityServiceFacade"); .getBean("identityServiceFacade");
jitProvisioningHandler = (IdentityServiceJITProvisioningHandler) childApplicationContextFactory.getApplicationContext() jitProvisioningHandler = (IdentityServiceJITProvisioningHandler) childApplicationContextFactory.getApplicationContext()
.getBean("jitProvisioningHandler"); .getBean("jitProvisioningHandler");
IdentityServiceConfig identityServiceConfig = (IdentityServiceConfig) childApplicationContextFactory.getApplicationContext() IdentityServiceConfig identityServiceConfig = (IdentityServiceConfig) childApplicationContextFactory.getApplicationContext()
.getBean("identityServiceConfig"); .getBean("identityServiceConfig");
identityServiceConfig.setAllowAnyHostname(true); identityServiceConfig.setAllowAnyHostname(true);
identityServiceConfig.setClientKeystore(null); identityServiceConfig.setClientKeystore(null);
identityServiceConfig.setDisableTrustManager(true); identityServiceConfig.setDisableTrustManager(true);
@@ -95,12 +95,11 @@ public class IdentityServiceJITProvisioningHandlerTest extends BaseSpringTest
{ {
assertFalse(personService.personExists(IDS_USERNAME)); assertFalse(personService.personExists(IDS_USERNAME));
IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
identityServiceFacade.authorize(
IdentityServiceFacade.AuthorizationGrant.password(IDS_USERNAME, userPassword)); IdentityServiceFacade.AuthorizationGrant.password(IDS_USERNAME, userPassword));
Optional<OIDCUserInfo> userInfoOptional = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> userInfoOptional = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
accessTokenAuthorization.getAccessToken().getTokenValue()); accessTokenAuthorization.getAccessToken().getTokenValue());
NodeRef person = personService.getPerson(IDS_USERNAME); NodeRef person = personService.getPerson(IDS_USERNAME);
@@ -125,23 +124,26 @@ public class IdentityServiceJITProvisioningHandlerTest extends BaseSpringTest
assertFalse(personService.personExists(IDS_USERNAME)); assertFalse(personService.personExists(IDS_USERNAME));
String principalAttribute = isAuth0Enabled ? PersonClaims.NICKNAME_CLAIM_NAME : PersonClaims.PREFERRED_USERNAME_CLAIM_NAME; String principalAttribute = isAuth0Enabled ? PersonClaims.NICKNAME_CLAIM_NAME : PersonClaims.PREFERRED_USERNAME_CLAIM_NAME;
IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
identityServiceFacade.authorize(
IdentityServiceFacade.AuthorizationGrant.password(IDS_USERNAME, userPassword)); IdentityServiceFacade.AuthorizationGrant.password(IDS_USERNAME, userPassword));
UserInfoAttrMapping userInfoAttrMapping = new UserInfoAttrMapping(principalAttribute, "given_name", "family_name", "email");
String accessToken = accessTokenAuthorization.getAccessToken().getTokenValue(); String accessToken = accessTokenAuthorization.getAccessToken().getTokenValue();
ClientRegistration clientRegistration = mock(ClientRegistration.class, RETURNS_DEEP_STUBS);
when(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).thenReturn(principalAttribute);
IdentityServiceFacade idsServiceFacadeMock = mock(IdentityServiceFacade.class); IdentityServiceFacade idsServiceFacadeMock = mock(IdentityServiceFacade.class);
when(idsServiceFacadeMock.decodeToken(accessToken)).thenReturn(null); when(idsServiceFacadeMock.decodeToken(accessToken)).thenReturn(null);
when(idsServiceFacadeMock.getUserInfo(accessToken, principalAttribute)).thenReturn(identityServiceFacade.getUserInfo(accessToken, principalAttribute)); when(idsServiceFacadeMock.getUserInfo(accessToken, userInfoAttrMapping)).thenReturn(identityServiceFacade.getUserInfo(accessToken, userInfoAttrMapping));
when(idsServiceFacadeMock.getClientRegistration()).thenReturn(clientRegistration);
// Replace the original facade with a mocked one to prevent user information from being extracted from the access token. // Replace the original facade with a mocked one to prevent user information from being extracted from the access token.
Field declaredField = jitProvisioningHandler.getClass() Field declaredField = jitProvisioningHandler.getClass()
.getDeclaredField("identityServiceFacade"); .getDeclaredField("identityServiceFacade");
declaredField.setAccessible(true); declaredField.setAccessible(true);
declaredField.set(jitProvisioningHandler, idsServiceFacadeMock); declaredField.set(jitProvisioningHandler, idsServiceFacadeMock);
Optional<OIDCUserInfo> userInfoOptional = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> userInfoOptional = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
accessToken); accessToken);
declaredField.set(jitProvisioningHandler, identityServiceFacade); declaredField.set(jitProvisioningHandler, identityServiceFacade);
@@ -153,7 +155,7 @@ public class IdentityServiceJITProvisioningHandlerTest extends BaseSpringTest
assertEquals("johndoe123@alfresco.com", userInfoOptional.get().email()); assertEquals("johndoe123@alfresco.com", userInfoOptional.get().email());
assertEquals("johndoe123@alfresco.com", nodeService.getProperty(person, ContentModel.PROP_EMAIL)); assertEquals("johndoe123@alfresco.com", nodeService.getProperty(person, ContentModel.PROP_EMAIL));
verify(idsServiceFacadeMock).decodeToken(accessToken); verify(idsServiceFacadeMock).decodeToken(accessToken);
verify(idsServiceFacadeMock, atLeast(1)).getUserInfo(accessToken, principalAttribute); verify(idsServiceFacadeMock, atLeast(1)).getUserInfo(accessToken, userInfoAttrMapping);
if (!isAuth0Enabled) if (!isAuth0Enabled)
{ {
assertEquals("John", userInfoOptional.get().firstName()); assertEquals("John", userInfoOptional.get().firstName());
@@ -166,18 +168,17 @@ public class IdentityServiceJITProvisioningHandlerTest extends BaseSpringTest
@After @After
public void tearDown() public void tearDown()
{ {
AuthenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<Void>() AuthenticationUtil.runAsSystem(new AuthenticationUtil.RunAsWork<Void>() {
{
@Override @Override
public Void doWork() throws Exception public Void doWork() throws Exception
{ {
transactionService.getRetryingTransactionHelper() transactionService.getRetryingTransactionHelper()
.doInTransaction((RetryingTransactionCallback<Void>) () -> { .doInTransaction((RetryingTransactionCallback<Void>) () -> {
personService.deletePerson(IDS_USERNAME); personService.deletePerson(IDS_USERNAME);
return null; return null;
}); });
return null; return null;
} }
}); });
} }
} }

View File

@@ -38,12 +38,17 @@ import static org.mockito.MockitoAnnotations.initMocks;
import java.util.Optional; import java.util.Optional;
import com.nimbusds.openid.connect.sdk.claims.PersonClaims; import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Answers;
import org.mockito.Mock; import org.mockito.Mock;
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.OIDCUserInfo;
import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService;
public class IdentityServiceJITProvisioningHandlerUnitTest public class IdentityServiceJITProvisioningHandlerUnitTest
{ {
@@ -51,6 +56,9 @@ public class IdentityServiceJITProvisioningHandlerUnitTest
@Mock @Mock
private IdentityServiceFacade identityServiceFacade; private IdentityServiceFacade identityServiceFacade;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ClientRegistration clientRegistration;
@Mock @Mock
private PersonService personService; private PersonService personService;
@@ -64,11 +72,22 @@ public class IdentityServiceJITProvisioningHandlerUnitTest
private IdentityServiceConfig identityServiceConfig; private IdentityServiceConfig identityServiceConfig;
@Mock @Mock
private OIDCUserInfo userInfo; private DecodedTokenUser decodedTokenUser;
private IdentityServiceJITProvisioningHandler jitProvisioningHandler; private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
private UserInfoAttrMapping expectedMapping;
private static final String JWT_TOKEN = "myToken"; private static final String JWT_TOKEN = "myToken";
private static final String USERNAME = "johny123";
private static final String FIRST_NAME = "John";
private static final String LAST_NAME = "Doe";
private static final String EMAIL = "johny123@email.com";
public static final String USERNAME_CLAIM = "nickname";
public static final String EMAIL_CLAIM = "email";
public static final String FIRST_NAME_CLAIM = "given_name";
public static final String LAST_NAME_CLAIM = "family_name";
@Before @Before
public void setup() public void setup()
@@ -78,149 +97,147 @@ public class IdentityServiceJITProvisioningHandlerUnitTest
when(transactionService.isReadOnly()).thenReturn(false); when(transactionService.isReadOnly()).thenReturn(false);
when(identityServiceFacade.decodeToken(JWT_TOKEN)).thenReturn(decodedAccessToken); when(identityServiceFacade.decodeToken(JWT_TOKEN)).thenReturn(decodedAccessToken);
when(personService.createMissingPeople()).thenReturn(true); when(personService.createMissingPeople()).thenReturn(true);
jitProvisioningHandler = new IdentityServiceJITProvisioningHandler(identityServiceFacade, when(identityServiceFacade.getClientRegistration()).thenReturn(clientRegistration);
personService, transactionService, identityServiceConfig); when(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).thenReturn(USERNAME_CLAIM);
when(identityServiceConfig.getEmailAttribute()).thenReturn(EMAIL_CLAIM);
when(identityServiceConfig.getFirstNameAttribute()).thenReturn(FIRST_NAME_CLAIM);
when(identityServiceConfig.getLastNameAttribute()).thenReturn(LAST_NAME_CLAIM);
expectedMapping = new UserInfoAttrMapping(USERNAME_CLAIM, FIRST_NAME_CLAIM, LAST_NAME_CLAIM, EMAIL_CLAIM);
jitProvisioningHandler = new IdentityServiceJITProvisioningHandler(identityServiceFacade, personService, transactionService, identityServiceConfig);
} }
@Test @Test
public void shouldExtractUserInfoForExistingUser() public void shouldExtractUserInfoForExistingUser()
{ {
when(personService.personExists("johny123")).thenReturn(true); when(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).thenReturn(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME);
when(decodedAccessToken.getClaim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn("johny123"); when(personService.personExists(USERNAME)).thenReturn(true);
when(decodedAccessToken.getClaim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn(USERNAME);
jitProvisioningHandler = new IdentityServiceJITProvisioningHandler(identityServiceFacade, personService, transactionService, identityServiceConfig);
Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
JWT_TOKEN); JWT_TOKEN);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertEquals("johny123", result.get().username()); assertEquals(USERNAME, result.get().username());
assertFalse(result.get().allFieldsNotEmpty()); assertFalse(result.get().allFieldsNotEmpty());
verify(identityServiceFacade, never()).getUserInfo(JWT_TOKEN, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME); verify(identityServiceFacade, never()).getUserInfo(JWT_TOKEN, expectedMapping);
} }
@Test @Test
public void shouldExtractUserInfoForExistingUserWithProviderPrincipalAttribute() public void shouldExtractUserInfoForExistingUserWithProviderPrincipalAttribute()
{ {
when(identityServiceConfig.getPrincipalAttribute()).thenReturn("nickname"); when(identityServiceConfig.getPrincipalAttribute()).thenReturn(USERNAME_CLAIM);
when(personService.personExists("johny123")).thenReturn(true); when(personService.personExists(USERNAME)).thenReturn(true);
when(decodedAccessToken.getClaim("nickname")).thenReturn("johny123"); when(decodedAccessToken.getClaim(USERNAME_CLAIM)).thenReturn(USERNAME);
Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
JWT_TOKEN); JWT_TOKEN);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertEquals("johny123", result.get().username()); assertEquals(USERNAME, result.get().username());
assertFalse(result.get().allFieldsNotEmpty()); assertFalse(result.get().allFieldsNotEmpty());
verify(identityServiceFacade, never()).getUserInfo(JWT_TOKEN, "nickname"); verify(identityServiceFacade, never()).getUserInfo(JWT_TOKEN, expectedMapping);
} }
@Test @Test
public void shouldExtractUserInfoFromAccessTokenAndCreateUser() public void shouldExtractUserInfoFromAccessTokenAndCreateUser()
{ {
when(personService.personExists("johny123")).thenReturn(false); when(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()).thenReturn(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME);
when(personService.personExists(USERNAME)).thenReturn(false);
when(decodedAccessToken.getClaim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn("johny123"); when(decodedAccessToken.getClaim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn(USERNAME);
when(decodedAccessToken.getClaim(PersonClaims.GIVEN_NAME_CLAIM_NAME)).thenReturn("John"); when(decodedAccessToken.getClaim(PersonClaims.GIVEN_NAME_CLAIM_NAME)).thenReturn(FIRST_NAME);
when(decodedAccessToken.getClaim(PersonClaims.FAMILY_NAME_CLAIM_NAME)).thenReturn("Doe"); when(decodedAccessToken.getClaim(PersonClaims.FAMILY_NAME_CLAIM_NAME)).thenReturn(LAST_NAME);
when(decodedAccessToken.getClaim(PersonClaims.EMAIL_CLAIM_NAME)).thenReturn("johny123@email.com"); when(decodedAccessToken.getClaim(PersonClaims.EMAIL_CLAIM_NAME)).thenReturn(EMAIL);
jitProvisioningHandler = new IdentityServiceJITProvisioningHandler(identityServiceFacade, personService, transactionService, identityServiceConfig);
Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
JWT_TOKEN); JWT_TOKEN);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertEquals("johny123", result.get().username()); assertEquals(USERNAME, result.get().username());
assertEquals("John", result.get().firstName()); assertEquals(FIRST_NAME, result.get().firstName());
assertEquals("Doe", result.get().lastName()); assertEquals(LAST_NAME, result.get().lastName());
assertEquals("johny123@email.com", result.get().email()); assertEquals(EMAIL, result.get().email());
assertTrue(result.get().allFieldsNotEmpty()); assertTrue(result.get().allFieldsNotEmpty());
verify(personService).createPerson(any()); verify(personService).createPerson(any());
verify(identityServiceFacade, never()).getUserInfo(JWT_TOKEN, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME); verify(identityServiceFacade, never()).getUserInfo(JWT_TOKEN, expectedMapping);
} }
@Test @Test
public void shouldExtractUserInfoFromUserInfoEndpointAndCreateUser() public void shouldExtractUserInfoFromUserInfoEndpointAndCreateUser()
{ {
when(userInfo.username()).thenReturn("johny123"); when(decodedTokenUser.username()).thenReturn(USERNAME);
when(userInfo.firstName()).thenReturn("John"); when(decodedTokenUser.firstName()).thenReturn(FIRST_NAME);
when(userInfo.lastName()).thenReturn("Doe"); when(decodedTokenUser.lastName()).thenReturn(LAST_NAME);
when(userInfo.email()).thenReturn("johny123@email.com"); when(decodedTokenUser.email()).thenReturn(EMAIL);
when(personService.personExists(USERNAME)).thenReturn(false);
when(personService.personExists("johny123")).thenReturn(false); when(decodedAccessToken.getClaim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn(USERNAME);
when(identityServiceFacade.getUserInfo(JWT_TOKEN, expectedMapping)).thenReturn(Optional.of(decodedTokenUser));
when(decodedAccessToken.getClaim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn("johny123");
when(identityServiceFacade.getUserInfo(JWT_TOKEN, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn(Optional.of(userInfo));
Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
JWT_TOKEN); JWT_TOKEN);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertEquals("johny123", result.get().username()); assertEquals(USERNAME, result.get().username());
assertEquals("John", result.get().firstName()); assertEquals(FIRST_NAME, result.get().firstName());
assertEquals("Doe", result.get().lastName()); assertEquals(LAST_NAME, result.get().lastName());
assertEquals("johny123@email.com", result.get().email()); assertEquals(EMAIL, result.get().email());
assertTrue(result.get().allFieldsNotEmpty()); assertTrue(result.get().allFieldsNotEmpty());
verify(personService).createPerson(any()); verify(personService).createPerson(any());
verify(identityServiceFacade).getUserInfo(JWT_TOKEN, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME); verify(identityServiceFacade).getUserInfo(JWT_TOKEN, expectedMapping);
} }
@Test @Test
public void shouldReturnEmptyOptionalIfUsernameNotExtracted() public void shouldReturnEmptyOptionalIfUsernameNotExtracted()
{ {
when(identityServiceFacade.getUserInfo(JWT_TOKEN, expectedMapping)).thenReturn(Optional.of(decodedTokenUser));
when(identityServiceFacade.getUserInfo(JWT_TOKEN, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn(Optional.of(userInfo));
Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
JWT_TOKEN); JWT_TOKEN);
assertFalse(result.isPresent()); assertFalse(result.isPresent());
verify(personService, never()).createPerson(any()); verify(personService, never()).createPerson(any());
verify(identityServiceFacade).getUserInfo(JWT_TOKEN, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME); verify(identityServiceFacade).getUserInfo(JWT_TOKEN, expectedMapping);
} }
@Test @Test
public void shouldCallUserInfoEndpointToGetUsername() public void shouldCallUserInfoEndpointToGetUsername()
{ {
when(personService.personExists("johny123")).thenReturn(true); when(personService.personExists(USERNAME)).thenReturn(true);
when(decodedAccessToken.getClaim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn(""); when(decodedAccessToken.getClaim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn("");
when(identityServiceFacade.getUserInfo(JWT_TOKEN, expectedMapping)).thenReturn(Optional.of(DecodedTokenUser.validateAndCreate(USERNAME, null, null, null)));
when(userInfo.username()).thenReturn("johny123");
when(identityServiceFacade.getUserInfo(JWT_TOKEN, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)).thenReturn(Optional.of(userInfo));
Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
JWT_TOKEN); JWT_TOKEN);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertEquals("johny123", result.get().username()); assertEquals(USERNAME, result.get().username());
assertEquals("", result.get().firstName()); assertEquals("", result.get().firstName());
assertEquals("", result.get().lastName()); assertEquals("", result.get().lastName());
assertEquals("", result.get().email()); assertEquals("", result.get().email());
assertFalse(result.get().allFieldsNotEmpty()); assertFalse(result.get().allFieldsNotEmpty());
verify(personService, never()).createPerson(any()); verify(personService, never()).createPerson(any());
verify(identityServiceFacade).getUserInfo(JWT_TOKEN, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME); verify(identityServiceFacade).getUserInfo(JWT_TOKEN, expectedMapping);
} }
@Test @Test
public void shouldCallUserInfoEndpointToGetUsernameWithProvidedPrincipalAttribute() public void shouldCallUserInfoEndpointToGetUsernameWithProvidedPrincipalAttribute()
{ {
when(identityServiceConfig.getPrincipalAttribute()).thenReturn("nickname"); when(identityServiceConfig.getPrincipalAttribute()).thenReturn(USERNAME_CLAIM);
when(personService.personExists("johny123")).thenReturn(true); when(personService.personExists(USERNAME)).thenReturn(true);
when(decodedAccessToken.getClaim(USERNAME_CLAIM)).thenReturn("");
when(decodedAccessToken.getClaim("nickname")).thenReturn(""); when(identityServiceFacade.getUserInfo(JWT_TOKEN, expectedMapping)).thenReturn(Optional.of(DecodedTokenUser.validateAndCreate(USERNAME, null, null, null)));
when(userInfo.username()).thenReturn("johny123");
when(identityServiceFacade.getUserInfo(JWT_TOKEN, "nickname")).thenReturn(Optional.of(userInfo));
Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded( Optional<OIDCUserInfo> result = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(
JWT_TOKEN); JWT_TOKEN);
assertTrue(result.isPresent()); assertTrue(result.isPresent());
assertEquals("johny123", result.get().username()); assertEquals(USERNAME, result.get().username());
assertEquals("", result.get().firstName()); assertEquals("", result.get().firstName());
assertEquals("", result.get().lastName()); assertEquals("", result.get().lastName());
assertEquals("", result.get().email()); assertEquals("", result.get().email());
assertFalse(result.get().allFieldsNotEmpty()); assertFalse(result.get().allFieldsNotEmpty());
verify(personService, never()).createPerson(any()); verify(personService, never()).createPerson(any());
verify(identityServiceFacade).getUserInfo(JWT_TOKEN, "nickname"); verify(identityServiceFacade).getUserInfo(JWT_TOKEN, expectedMapping);
} }
@Test @Test
@@ -232,8 +249,8 @@ public class IdentityServiceJITProvisioningHandlerUnitTest
verify(personService, never()).createPerson(any()); verify(personService, never()).createPerson(any());
verify(identityServiceFacade, never()).decodeToken(null); verify(identityServiceFacade, never()).decodeToken(null);
verify(identityServiceFacade, never()).decodeToken(""); verify(identityServiceFacade, never()).decodeToken("");
verify(identityServiceFacade, never()).getUserInfo(null, PersonClaims.PREFERRED_USERNAME_CLAIM_NAME); verify(identityServiceFacade, never()).getUserInfo(null, expectedMapping);
verify(identityServiceFacade, never()).getUserInfo("", PersonClaims.PREFERRED_USERNAME_CLAIM_NAME); verify(identityServiceFacade, never()).getUserInfo(null, expectedMapping);
} }
} }

View File

@@ -34,17 +34,18 @@ import java.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.Vector; import java.util.Vector;
import java.util.function.Supplier; import java.util.function.Supplier;
import jakarta.servlet.http.HttpServletRequest;
import com.nimbusds.openid.connect.sdk.claims.PersonClaims; import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
import jakarta.servlet.http.HttpServletRequest;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.mockito.Mockito;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.DecodedAccessToken; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.DecodedAccessToken;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.TokenDecodingException; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.TokenDecodingException;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
/** /**
* Tests the Identity Service based authentication subsystem. * Tests the Identity Service based authentication subsystem.
@@ -68,7 +69,9 @@ public class IdentityServiceRemoteUserMapperTest extends TestCase
public void testWrongTokenWithSilentValidation() public void testWrongTokenWithSilentValidation()
{ {
final IdentityServiceRemoteUserMapper mapper = givenMapper(Map.of("WrOnG-ToKeN", () -> {throw new TokenDecodingException("Expected ");})); final IdentityServiceRemoteUserMapper mapper = givenMapper(Map.of("WrOnG-ToKeN", () -> {
throw new TokenDecodingException("Expected ");
}));
mapper.setValidationFailureSilent(true); mapper.setValidationFailureSilent(true);
HttpServletRequest mockRequest = createMockTokenRequest("WrOnG-ToKeN"); HttpServletRequest mockRequest = createMockTokenRequest("WrOnG-ToKeN");
@@ -79,7 +82,9 @@ public class IdentityServiceRemoteUserMapperTest extends TestCase
public void testWrongTokenWithoutSilentValidation() public void testWrongTokenWithoutSilentValidation()
{ {
final IdentityServiceRemoteUserMapper mapper = givenMapper(Map.of("WrOnG-ToKeN", () -> {throw new TokenDecodingException("Expected");})); final IdentityServiceRemoteUserMapper mapper = givenMapper(Map.of("WrOnG-ToKeN", () -> {
throw new TokenDecodingException("Expected");
}));
mapper.setValidationFailureSilent(false); mapper.setValidationFailureSilent(false);
HttpServletRequest mockRequest = createMockTokenRequest("WrOnG-ToKeN"); HttpServletRequest mockRequest = createMockTokenRequest("WrOnG-ToKeN");
@@ -92,12 +97,14 @@ public class IdentityServiceRemoteUserMapperTest extends TestCase
private IdentityServiceRemoteUserMapper givenMapper(Map<String, Supplier<String>> tokenToUser) private IdentityServiceRemoteUserMapper givenMapper(Map<String, Supplier<String>> tokenToUser)
{ {
final TransactionService transactionService = mock(TransactionService.class); final TransactionService transactionService = mock(TransactionService.class);
final IdentityServiceFacade facade = mock(IdentityServiceFacade.class); final IdentityServiceFacade facade = mock(IdentityServiceFacade.class, Mockito.RETURNS_DEEP_STUBS);
final PersonService personService = mock(PersonService.class); final PersonService personService = mock(PersonService.class);
final IdentityServiceConfig identityServiceConfig = mock(IdentityServiceConfig.class); final IdentityServiceConfig identityServiceConfig = mock(IdentityServiceConfig.class);
when(transactionService.isReadOnly()).thenReturn(true); when(transactionService.isReadOnly()).thenReturn(true);
when(facade.decodeToken(anyString())) when(facade.decodeToken(anyString()))
.thenAnswer(i -> new TestDecodedToken(tokenToUser.get(i.getArgument(0, String.class)))); .thenAnswer(i -> new TestDecodedToken(tokenToUser.get(i.getArgument(0, String.class))));
when(facade.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName())
.thenReturn(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME);
when(personService.getUserIdentifier(anyString())).thenAnswer(i -> i.getArgument(0, String.class)); when(personService.getUserIdentifier(anyString())).thenAnswer(i -> i.getArgument(0, String.class));
@@ -108,21 +115,21 @@ public class IdentityServiceRemoteUserMapperTest extends TestCase
mapper.setActive(true); mapper.setActive(true);
mapper.setBearerTokenResolver(new DefaultBearerTokenResolver()); mapper.setBearerTokenResolver(new DefaultBearerTokenResolver());
return mapper; return mapper;
} }
/** /**
* Utility method for creating a mocked Servlet request with a token. * Utility method for creating a mocked Servlet request with a token.
* *
* @param token The token to add to the Authorization header * @param token
* The token to add to the Authorization header
* @return The mocked request object * @return The mocked request object
*/ */
private HttpServletRequest createMockTokenRequest(String token) private HttpServletRequest createMockTokenRequest(String token)
{ {
// Mock a request with the token in the Authorization header (if supplied) // Mock a request with the token in the Authorization header (if supplied)
HttpServletRequest mockRequest = mock(HttpServletRequest.class); HttpServletRequest mockRequest = mock(HttpServletRequest.class);
Vector<String> authHeaderValues = new Vector<>(1); Vector<String> authHeaderValues = new Vector<>(1);
if (token != null) if (token != null)
{ {
@@ -133,7 +140,7 @@ public class IdentityServiceRemoteUserMapperTest extends TestCase
.thenReturn(authHeaderValues.elements()); .thenReturn(authHeaderValues.elements());
when(mockRequest.getHeader(AUTHORIZATION_HEADER)) when(mockRequest.getHeader(AUTHORIZATION_HEADER))
.thenReturn(authHeaderValues.isEmpty() ? null : authHeaderValues.get(0)); .thenReturn(authHeaderValues.isEmpty() ? null : authHeaderValues.get(0));
return mockRequest; return mockRequest;
} }
@@ -166,4 +173,4 @@ public class IdentityServiceRemoteUserMapperTest extends TestCase
return PersonClaims.PREFERRED_USERNAME_CLAIM_NAME.equals(claim) ? usernameSupplier.get() : null; return PersonClaims.PREFERRED_USERNAME_CLAIM_NAME.equals(claim) ? usernameSupplier.get() : null;
} }
} }
} }

View File

@@ -1,98 +1,99 @@
/* /*
* #%L * #%L
* Alfresco Repository * Alfresco Repository
* %% * %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited * Copyright (C) 2005 - 2025 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.security.authentication.identityservice; package org.alfresco.repo.security.authentication.identityservice;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException; import org.junit.Test;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.TokenDecodingException; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.junit.Test; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.web.client.RestOperations;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
import org.springframework.web.client.RestOperations; import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.TokenDecodingException;
public class SpringBasedIdentityServiceFacadeUnitTest import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
{
private static final String USER_NAME = "user"; public class SpringBasedIdentityServiceFacadeUnitTest
private static final String PASSWORD = "password"; {
private static final String TOKEN = "tEsT-tOkEn"; private static final String USER_NAME = "user";
private static final String PASSWORD = "password";
@Test private static final String TOKEN = "tEsT-tOkEn";
public void shouldThrowVerificationExceptionOnFailure() private static final UserInfoAttrMapping USER_INFO_ATTR_MAPPING = new UserInfoAttrMapping("preferred_username", "given_name", "family_name", "email");
{
final RestOperations restOperations = mock(RestOperations.class); @Test
final JwtDecoder jwtDecoder = mock(JwtDecoder.class); public void shouldThrowVerificationExceptionOnFailure()
when(restOperations.exchange(any(), any(Class.class))).thenThrow(new RuntimeException("Expected")); {
final RestOperations restOperations = mock(RestOperations.class);
final SpringBasedIdentityServiceFacade facade = new SpringBasedIdentityServiceFacade(restOperations, testRegistration(), jwtDecoder); final JwtDecoder jwtDecoder = mock(JwtDecoder.class);
when(restOperations.exchange(any(), any(Class.class))).thenThrow(new RuntimeException("Expected"));
assertThatExceptionOfType(AuthorizationException.class)
.isThrownBy(() -> facade.authorize(AuthorizationGrant.password(USER_NAME, PASSWORD))) final SpringBasedIdentityServiceFacade facade = new SpringBasedIdentityServiceFacade(restOperations, testRegistration(), jwtDecoder);
.havingCause().withNoCause().withMessage("Expected");
} assertThatExceptionOfType(AuthorizationException.class)
.isThrownBy(() -> facade.authorize(AuthorizationGrant.password(USER_NAME, PASSWORD)))
@Test .havingCause().withNoCause().withMessage("Expected");
public void shouldThrowTokenExceptionOnFailure() }
{
final RestOperations restOperations = mock(RestOperations.class); @Test
final JwtDecoder jwtDecoder = mock(JwtDecoder.class); public void shouldThrowTokenExceptionOnFailure()
when(jwtDecoder.decode(TOKEN)).thenThrow(new RuntimeException("Expected")); {
final RestOperations restOperations = mock(RestOperations.class);
final SpringBasedIdentityServiceFacade facade = new SpringBasedIdentityServiceFacade(restOperations, testRegistration(), jwtDecoder); final JwtDecoder jwtDecoder = mock(JwtDecoder.class);
when(jwtDecoder.decode(TOKEN)).thenThrow(new RuntimeException("Expected"));
assertThatExceptionOfType(TokenDecodingException.class)
.isThrownBy(() -> facade.decodeToken(TOKEN)) final SpringBasedIdentityServiceFacade facade = new SpringBasedIdentityServiceFacade(restOperations, testRegistration(), jwtDecoder);
.havingCause().withNoCause().withMessage("Expected");
} assertThatExceptionOfType(TokenDecodingException.class)
.isThrownBy(() -> facade.decodeToken(TOKEN))
.havingCause().withNoCause().withMessage("Expected");
@Test }
public void shouldReturnEmptyOptionalOnFailure()
{ @Test
final RestOperations restOperations = mock(RestOperations.class); public void shouldReturnEmptyOptionalOnFailure()
final JwtDecoder jwtDecoder = mock(JwtDecoder.class); {
final SpringBasedIdentityServiceFacade facade = new SpringBasedIdentityServiceFacade(restOperations, testRegistration(), jwtDecoder); final RestOperations restOperations = mock(RestOperations.class);
final JwtDecoder jwtDecoder = mock(JwtDecoder.class);
final SpringBasedIdentityServiceFacade facade = new SpringBasedIdentityServiceFacade(restOperations, testRegistration(), jwtDecoder);
assertThat(facade.getUserInfo(TOKEN, "preferred_username").isEmpty()).isTrue();
} assertThat(facade.getUserInfo(TOKEN, USER_INFO_ATTR_MAPPING).isEmpty()).isTrue();
}
private ClientRegistration testRegistration()
{ private ClientRegistration testRegistration()
return ClientRegistration.withRegistrationId("test") {
.tokenUri("http://localhost") return ClientRegistration.withRegistrationId("test")
.clientId("test") .tokenUri("http://localhost")
.userInfoUri("http://localhost/userinfo") .clientId("test")
.authorizationGrantType(AuthorizationGrantType.PASSWORD) .userInfoUri("http://localhost/userinfo")
.build(); .authorizationGrantType(AuthorizationGrantType.PASSWORD)
} .build();
} }
}

View File

@@ -38,18 +38,11 @@ import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Set;
import com.nimbusds.oauth2.sdk.Scope;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig; import com.nimbusds.oauth2.sdk.Scope;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessToken;
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.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
@@ -58,6 +51,14 @@ import org.mockito.Mock;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
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.AccessToken;
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;
@SuppressWarnings("PMD.AvoidStringBufferField") @SuppressWarnings("PMD.AvoidStringBufferField")
public class IdentityServiceAdminConsoleAuthenticatorUnitTest public class IdentityServiceAdminConsoleAuthenticatorUnitTest
{ {
@@ -118,7 +119,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
{ {
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("JWT_TOKEN"); when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn( when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().plusSeconds(60).toEpochMilli())); String.valueOf(Instant.now().plusSeconds(60).toEpochMilli()));
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin"); when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getAdminConsoleUser(request, response); String username = authenticator.getAdminConsoleUser(request, response);
@@ -134,7 +135,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN"); when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN"); when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn( when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().minusSeconds(60).toEpochMilli())); String.valueOf(Instant.now().minusSeconds(60).toEpochMilli()));
when(accessToken.getTokenValue()).thenReturn("REFRESHED_JWT_TOKEN"); when(accessToken.getTokenValue()).thenReturn("REFRESHED_JWT_TOKEN");
when(accessToken.getExpiresAt()).thenReturn(Instant.now().plusSeconds(60)); when(accessToken.getExpiresAt()).thenReturn(Instant.now().plusSeconds(60));
when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken); when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken);
@@ -155,10 +156,11 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
{ {
String redirectPath = "/alfresco/s/admin/admin-communitysummary"; String redirectPath = "/alfresco/s/admin/admin-communitysummary";
when(identityServiceConfig.getAdminConsoleScopes()).thenReturn(Set.of("openid", "email", "profile", "offline_access"));
when(identityServiceConfig.getAdminConsoleRedirectPath()).thenReturn("/alfresco/s/admin/admin-communitysummary"); when(identityServiceConfig.getAdminConsoleRedirectPath()).thenReturn("/alfresco/s/admin/admin-communitysummary");
ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class);
String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope=" String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope="
.formatted("http://localhost:8080", redirectPath); .formatted("http://localhost:8080", redirectPath);
authenticator.requestAuthentication(request, response); authenticator.requestAuthentication(request, response);
@@ -178,9 +180,10 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
String redirectPath = "/alfresco/s/admin/admin-communitysummary"; String redirectPath = "/alfresco/s/admin/admin-communitysummary";
when(identityServiceConfig.getAudience()).thenReturn(audience); when(identityServiceConfig.getAudience()).thenReturn(audience);
when(identityServiceConfig.getAdminConsoleRedirectPath()).thenReturn(redirectPath); when(identityServiceConfig.getAdminConsoleRedirectPath()).thenReturn(redirectPath);
when(identityServiceConfig.getAdminConsoleScopes()).thenReturn(Set.of("openid", "email", "profile", "offline_access"));
ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class);
String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope=" String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope="
.formatted("http://localhost:8080", redirectPath); .formatted("http://localhost:8080", redirectPath);
authenticator.requestAuthentication(request, response); authenticator.requestAuthentication(request, response);
@@ -200,7 +203,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN"); when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN"); when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn( when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().minusSeconds(60).toEpochMilli())); String.valueOf(Instant.now().minusSeconds(60).toEpochMilli()));
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenThrow(AuthorizationException.class); when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenThrow(AuthorizationException.class);
@@ -221,8 +224,8 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken); when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken);
when(accessTokenAuthorization.getRefreshTokenValue()).thenReturn("REFRESH_TOKEN"); when(accessTokenAuthorization.getRefreshTokenValue()).thenReturn("REFRESH_TOKEN");
when(identityServiceFacade.authorize( when(identityServiceFacade.authorize(
AuthorizationGrant.authorizationCode("auth_code", adminConsoleURL.toString()))) AuthorizationGrant.authorizationCode("auth_code", adminConsoleURL.toString())))
.thenReturn(accessTokenAuthorization); .thenReturn(accessTokenAuthorization);
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin"); when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getAdminConsoleUser(request, response); String username = authenticator.getAdminConsoleUser(request, response);
@@ -242,4 +245,4 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
assertEquals("admin", username); assertEquals("admin", username);
} }
} }

View File

@@ -0,0 +1,109 @@
/*
* #%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.user;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
public class AccessTokenToDecodedTokenUserMapperUnitTest
{
@Mock
private IdentityServiceFacade.DecodedAccessToken decodedAccessToken;
private AccessTokenToDecodedTokenUserMapper tokenToDecodedTokenUserMapper;
public static final String USERNAME_CLAIM = "nickname";
public static final String EMAIL_CLAIM = "email";
public static final String FIRST_NAME_CLAIM = "given_name";
public static final String LAST_NAME_CLAIM = "family_name";
@Before
public void setup()
{
initMocks(this);
UserInfoAttrMapping userInfoAttrMapping = new UserInfoAttrMapping(USERNAME_CLAIM, FIRST_NAME_CLAIM, LAST_NAME_CLAIM, EMAIL_CLAIM);
tokenToDecodedTokenUserMapper = new AccessTokenToDecodedTokenUserMapper(userInfoAttrMapping);
}
@Test
public void shouldMapToDecodedTokenUserWithAllFieldsPopulated()
{
when(decodedAccessToken.getClaim(USERNAME_CLAIM)).thenReturn("johny123");
when(decodedAccessToken.getClaim(FIRST_NAME_CLAIM)).thenReturn("John");
when(decodedAccessToken.getClaim(LAST_NAME_CLAIM)).thenReturn("Doe");
when(decodedAccessToken.getClaim(EMAIL_CLAIM)).thenReturn("johny123@email.com");
Optional<DecodedTokenUser> result = tokenToDecodedTokenUserMapper.toDecodedTokenUser(decodedAccessToken);
assertTrue(result.isPresent());
assertEquals("johny123", result.get().username());
assertEquals("John", result.get().firstName());
assertEquals("Doe", result.get().lastName());
assertEquals("johny123@email.com", result.get().email());
}
@Test
public void shouldMapToDecodedTokenUserWithSomeFieldsEmpty()
{
when(decodedAccessToken.getClaim(USERNAME_CLAIM)).thenReturn("johny123");
when(decodedAccessToken.getClaim(FIRST_NAME_CLAIM)).thenReturn("");
when(decodedAccessToken.getClaim(LAST_NAME_CLAIM)).thenReturn("Doe");
when(decodedAccessToken.getClaim(EMAIL_CLAIM)).thenReturn("");
Optional<DecodedTokenUser> result = tokenToDecodedTokenUserMapper.toDecodedTokenUser(decodedAccessToken);
assertTrue(result.isPresent());
assertEquals("johny123", result.get().username());
assertEquals("", result.get().firstName());
assertEquals("Doe", result.get().lastName());
assertEquals("", result.get().email());
}
@Test
public void shouldReturnEmptyOptionalForNullUsername()
{
when(decodedAccessToken.getClaim(USERNAME_CLAIM)).thenReturn(null);
when(decodedAccessToken.getClaim(FIRST_NAME_CLAIM)).thenReturn("John");
when(decodedAccessToken.getClaim(LAST_NAME_CLAIM)).thenReturn("Doe");
when(decodedAccessToken.getClaim(EMAIL_CLAIM)).thenReturn("johny123@email.com");
Optional<DecodedTokenUser> result = tokenToDecodedTokenUserMapper.toDecodedTokenUser(decodedAccessToken);
assertFalse(result.isPresent());
}
}

View File

@@ -0,0 +1,95 @@
/*
* #%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.user;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.alfresco.service.cmr.security.PersonService;
public class TokenUserToOIDCUserMapperUnitTest
{
@Mock
private PersonService personService;
@InjectMocks
private TokenUserToOIDCUserMapper tokenUserToOIDCUserMapper;
@Before
public void setup()
{
initMocks(this);
}
@Test
public void shouldMapToOIDCUserWithAllFieldsPopulated()
{
DecodedTokenUser decodedTokenUser = new DecodedTokenUser("JOHNY123", "John", "Doe", "johny123@email.com");
when(personService.getUserIdentifier("JOHNY123")).thenReturn("johny123");
OIDCUserInfo oidcUserInfo = tokenUserToOIDCUserMapper.toOIDCUser(decodedTokenUser);
assertEquals("johny123", oidcUserInfo.username());
assertEquals("John", oidcUserInfo.firstName());
assertEquals("Doe", oidcUserInfo.lastName());
assertEquals("johny123@email.com", oidcUserInfo.email());
}
@Test
public void shouldMapToOIDCUserWithSomeFieldsEmpty()
{
DecodedTokenUser decodedTokenUser = new DecodedTokenUser("johny123", "", "Doe", "");
when(personService.getUserIdentifier("johny123")).thenReturn("johny123");
OIDCUserInfo oidcUserInfo = tokenUserToOIDCUserMapper.toOIDCUser(decodedTokenUser);
assertEquals("johny123", oidcUserInfo.username());
assertEquals("", oidcUserInfo.firstName());
assertEquals("Doe", oidcUserInfo.lastName());
assertEquals("", oidcUserInfo.email());
}
@Test
public void shouldReturnNullForNullUsername()
{
DecodedTokenUser decodedTokenUser = new DecodedTokenUser(null, "John", "Doe", "johny123@email.com");
OIDCUserInfo oidcUserInfo = tokenUserToOIDCUserMapper.toOIDCUser(decodedTokenUser);
assertNull(oidcUserInfo.username());
assertEquals("John", oidcUserInfo.firstName());
assertEquals("Doe", oidcUserInfo.lastName());
assertEquals("johny123@email.com", oidcUserInfo.email());
}
}

View File

@@ -28,6 +28,9 @@ identity-service.register-node-at-startup=true
identity-service.register-node-period=50 identity-service.register-node-period=50
identity-service.token-store=SESSION identity-service.token-store=SESSION
identity-service.principal-attribute=preferred_username identity-service.principal-attribute=preferred_username
identity-service.first-name-attribute=given_name
identity-service.last-name-attribute=family_name
identity-service.email-attribute=email
identity-service.turn-off-change-session-id-on-login=true identity-service.turn-off-change-session-id-on-login=true
identity-service.token-minimum-time-to-live=10 identity-service.token-minimum-time-to-live=10
identity-service.min-time-between-jwks-requests=60 identity-service.min-time-between-jwks-requests=60