mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-11-05 15:32:21 +00:00
Compare commits
1 Commits
release/25
...
fix/t1_Ope
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d31842352d |
190
.github/workflows/ci.yml
vendored
190
.github/workflows/ci.yml
vendored
@@ -44,10 +44,14 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/pre-commit@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- id: changed-files
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/github-list-changes@v8.2.0
|
||||
with:
|
||||
write-list-to-env: true
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/pre-commit@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Prepare maven cache and check compilation"
|
||||
@@ -65,12 +69,12 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/veracode@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/veracode@v8.2.0
|
||||
continue-on-error: true
|
||||
with:
|
||||
srcclr-api-token: ${{ secrets.SRCCLR_API_TOKEN }}
|
||||
@@ -88,10 +92,10 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/github-download-file@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/github-download-file@v8.2.0
|
||||
with:
|
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }}
|
||||
repository: "Alfresco/veracode-baseline-archive"
|
||||
@@ -144,10 +148,10 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[skip tests]') &&
|
||||
!contains(github.event.head_commit.message, '[force]')
|
||||
steps:
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/ya-pmd-scan@v4.3.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- uses: Alfresco/ya-pmd-scan@v4.1.0
|
||||
with:
|
||||
classpath-build-command: "mvn test-compile -ntp -Pags -pl \"-:alfresco-community-repo-docker\""
|
||||
|
||||
@@ -177,14 +181,14 @@ jobs:
|
||||
testAttributes: "-Dtest=AllMmtUnitTestSuite"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testModule }}
|
||||
@@ -215,7 +219,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -257,9 +261,9 @@ jobs:
|
||||
REQUIRES_INSTALLED_ARTIFACTS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -272,7 +276,7 @@ jobs:
|
||||
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }}
|
||||
@@ -303,7 +307,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -336,9 +340,9 @@ jobs:
|
||||
version: ['10.5', '10.6']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: Run MariaDB ${{ matrix.version }} database
|
||||
@@ -347,7 +351,7 @@ jobs:
|
||||
MARIADB_VERSION: ${{ matrix.version }}
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.version }}
|
||||
@@ -378,7 +382,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -407,9 +411,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run MariaDB 10.11 database"
|
||||
@@ -418,7 +422,7 @@ jobs:
|
||||
MARIADB_VERSION: 10.11
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -449,7 +453,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -478,9 +482,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run MySQL 8 database"
|
||||
@@ -489,7 +493,7 @@ jobs:
|
||||
MYSQL_VERSION: 8
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -511,7 +515,7 @@ jobs:
|
||||
RP_OPTS: ${{ github.ref_name == 'master' && steps.rp-prepare.outputs.mvn-opts || '' }}
|
||||
run: |
|
||||
eval "args=($RP_OPTS)"
|
||||
mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=com.mysql.cj.jdbc.Driver -Ddb.name=alfresco -Ddb.url=jdbc:mysql://localhost:3307/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "${args[@]}"
|
||||
mvn -B test -pl repository -am -Dtest=AllDBTestsTestSuite -DfailIfNoTests=false -Ddb.driver=com.mysql.jdbc.Driver -Ddb.name=alfresco -Ddb.url=jdbc:mysql://localhost:3307/alfresco -Ddb.username=alfresco -Ddb.password=alfresco "${args[@]}"
|
||||
continue-on-error: true
|
||||
- name: "Update GitHub Step Summary"
|
||||
if: github.ref_name == 'master'
|
||||
@@ -520,7 +524,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -548,9 +552,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run PostgreSQL 14.15 database"
|
||||
@@ -559,7 +563,7 @@ jobs:
|
||||
POSTGRES_VERSION: 14.15
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -590,7 +594,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -618,9 +622,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run PostgreSQL 15.10 database"
|
||||
@@ -629,7 +633,7 @@ jobs:
|
||||
POSTGRES_VERSION: 15.10
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -660,7 +664,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -688,9 +692,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run PostgreSQL 16.6 database"
|
||||
@@ -699,7 +703,7 @@ jobs:
|
||||
POSTGRES_VERSION: 16.6
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -730,7 +734,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -756,16 +760,16 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run ActiveMQ"
|
||||
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile activemq up -d
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -796,7 +800,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -856,9 +860,9 @@ jobs:
|
||||
mvn-options: '-Dencryption.ssl.keystore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${CI_WORKSPACE}/keystores/alfresco/alfresco.truststore'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Set transformers tag"
|
||||
@@ -881,7 +885,7 @@ jobs:
|
||||
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile ${{ matrix.compose-profile }} up -d
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.testSuite }} ${{ matrix.idp }}
|
||||
@@ -912,7 +916,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -970,9 +974,9 @@ jobs:
|
||||
REQUIRES_LOCAL_IMAGES: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -988,7 +992,7 @@ jobs:
|
||||
run: mvn install -pl :alfresco-community-repo-integration-test -am -DskipTests -Pall-tas-tests
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} - ${{ matrix.test-name }}
|
||||
@@ -1026,7 +1030,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.tests.outcome }}
|
||||
@@ -1052,16 +1056,16 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- name: "Run Postgres 16.6 database"
|
||||
run: docker compose -f ./scripts/ci/docker-compose/docker-compose.yaml --profile postgres up -d
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -1092,7 +1096,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -1126,9 +1130,9 @@ jobs:
|
||||
REQUIRES_INSTALLED_ARTIFACTS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -1136,7 +1140,7 @@ jobs:
|
||||
bash ./scripts/ci/build.sh
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (PostgreSQL) ${{ matrix.test-name }}
|
||||
@@ -1172,9 +1176,9 @@ jobs:
|
||||
REQUIRES_INSTALLED_ARTIFACTS: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -1182,7 +1186,7 @@ jobs:
|
||||
bash ./scripts/ci/build.sh
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }} 0${{ matrix.part }} - (MySQL) ${{ matrix.test-name }}
|
||||
@@ -1214,9 +1218,9 @@ jobs:
|
||||
REQUIRES_LOCAL_IMAGES: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
@@ -1230,7 +1234,7 @@ jobs:
|
||||
mvn -B install -pl :alfresco-governance-services-automation-community-rest-api -am -Pags -Pall-tas-tests -DskipTests
|
||||
- name: "Prepare Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-prepare@v8.2.0
|
||||
id: rp-prepare
|
||||
with:
|
||||
rp-launch-prefix: ${{ env.RP_LAUNCH_PREFIX }}
|
||||
@@ -1262,7 +1266,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: "Summarize Report Portal"
|
||||
if: github.ref_name == 'master'
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.16.0
|
||||
uses: Alfresco/alfresco-build-tools/.github/actions/reportportal-summarize@v8.2.0
|
||||
id: rp-summarize
|
||||
with:
|
||||
tests-outcome: ${{ steps.run-tests.outcome }}
|
||||
@@ -1304,9 +1308,9 @@ jobs:
|
||||
!contains(github.event.head_commit.message, '[force]')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.16.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Build"
|
||||
timeout-minutes: ${{ fromJSON(env.GITHUB_ACTIONS_DEPLOY_TIMEOUT) }}
|
||||
run: |
|
||||
|
||||
16
.github/workflows/master_release.yml
vendored
16
.github/workflows/master_release.yml
vendored
@@ -34,12 +34,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.13.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.13.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v8.13.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v8.2.0
|
||||
with:
|
||||
username: ${{ env.GIT_USERNAME }}
|
||||
email: ${{ env.GIT_EMAIL }}
|
||||
@@ -63,12 +63,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.13.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.13.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.13.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.2.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.2.0
|
||||
- name: "Init"
|
||||
run: bash ./scripts/ci/init.sh
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v8.13.0
|
||||
- uses: Alfresco/alfresco-build-tools/.github/actions/configure-git-author@v8.2.0
|
||||
with:
|
||||
username: ${{ env.GIT_USERNAME }}
|
||||
email: ${{ env.GIT_EMAIL }}
|
||||
|
||||
@@ -133,21 +133,21 @@
|
||||
"filename": ".github/workflows/ci.yml",
|
||||
"hashed_secret": "b86dc2f033a63f2b7b9e7d270ab806d2910d7572",
|
||||
"is_verified": false,
|
||||
"line_number": 295
|
||||
"line_number": 299
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": ".github/workflows/ci.yml",
|
||||
"hashed_secret": "1bfb0e20f886150ba59b853bcd49dea893e00966",
|
||||
"is_verified": false,
|
||||
"line_number": 370
|
||||
"line_number": 374
|
||||
},
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": ".github/workflows/ci.yml",
|
||||
"hashed_secret": "128f14373ccfaff49e3664045d3a11b50cbb7b39",
|
||||
"is_verified": false,
|
||||
"line_number": 904
|
||||
"line_number": 908
|
||||
}
|
||||
],
|
||||
".github/workflows/master_release.yml": [
|
||||
@@ -1377,7 +1377,7 @@
|
||||
"filename": "repository/src/test/java/org/alfresco/repo/imap/ImapMessageTest.java",
|
||||
"hashed_secret": "d033e22ae348aeb5660fc2140aec35850c4da997",
|
||||
"is_verified": false,
|
||||
"line_number": 116,
|
||||
"line_number": 118,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -1431,6 +1431,26 @@
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
"repository/src/test/java/org/alfresco/repo/lock/LockBehaviourImplTest.java": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "repository/src/test/java/org/alfresco/repo/lock/LockBehaviourImplTest.java",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 112,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
"repository/src/test/java/org/alfresco/repo/lock/LockServiceImplTest.java": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
"filename": "repository/src/test/java/org/alfresco/repo/lock/LockServiceImplTest.java",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 103,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
"repository/src/test/java/org/alfresco/repo/management/JmxDumpUtilTest.java": [
|
||||
{
|
||||
"type": "Secret Keyword",
|
||||
@@ -1607,7 +1627,7 @@
|
||||
"filename": "repository/src/test/java/org/alfresco/repo/security/authentication/identityservice/SpringBasedIdentityServiceFacadeUnitTest.java",
|
||||
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
|
||||
"is_verified": false,
|
||||
"line_number": 48,
|
||||
"line_number": 46,
|
||||
"is_secret": false
|
||||
}
|
||||
],
|
||||
@@ -1868,5 +1888,5 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_at": "2025-05-13T13:17:41Z"
|
||||
"generated_at": "2024-12-19T08:58:42Z"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-amps</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -131,16 +131,6 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
|
||||
RestRequestQueryModel ancestorReq = getContentFromFolderAndAllSubfoldersQuery(rootFolder.getNodeRefWithoutVersion());
|
||||
SearchRequest ancestorSearchRequest = new SearchRequest();
|
||||
ancestorSearchRequest.setQuery(ancestorReq);
|
||||
|
||||
STEP("Wait until paths are indexed.");
|
||||
// to improve stability on CI - seems that sometimes during big load we need to wait longer for the condition
|
||||
await().atMost(120, TimeUnit.SECONDS)
|
||||
.until(() -> getRestAPIFactory().getSearchAPI(null).search(ancestorSearchRequest).getPagination()
|
||||
.getTotalItems() == NUMBER_OF_FILES);
|
||||
|
||||
holdBulkOperation = HoldBulkOperation.builder()
|
||||
.query(queryReq)
|
||||
.op(HoldBulkOperationType.ADD).build();
|
||||
|
||||
@@ -23,7 +23,7 @@ Recorded content can be explicitly destroyed whilst maintaining the original nod
|
||||
* License: Alfresco Community
|
||||
* Issue Tracker Link: [JIRA RM](https://issues.alfresco.com/jira/projects/RM/summary)
|
||||
* Contribution Model: Alfresco Closed Source
|
||||
* Documentation: [docs.alfresco.com (Records Management)](https://support.hyland.com/r/Alfresco/Alfresco-Governance-Services-Community-Edition/23.4/Alfresco-Governance-Services-Community-Edition/Introduction)
|
||||
* Documentation: [docs.alfresco.com (Records Management)](http://docs.alfresco.com/rm2.4/concepts/welcome-rm.html)
|
||||
|
||||
***
|
||||
|
||||
|
||||
@@ -21,18 +21,18 @@ RM is split into two main parts - a repository integration and a Share integrati
|
||||
* [Community License](../LICENSE.txt)
|
||||
* [Enterprise License](../../rm-enterprise/LICENSE.txt) (this file will only be present in clones of the Enterprise repository)
|
||||
* [Issue Tracker Link](https://issues.alfresco.com/jira/projects/RM)
|
||||
* [Community Documentation Link](https://support.hyland.com/r/Alfresco/Alfresco-Governance-Services-Community-Edition/23.4/Alfresco-Governance-Services-Community-Edition/Introduction)
|
||||
* [Enterprise Documentation Link](https://support.hyland.com/r/Alfresco/Alfresco-Governance-Services/23.4/Alfresco-Governance-Services/Introduction)
|
||||
* [Community Documentation Link](http://docs.alfresco.com/rm-community/concepts/welcome-rm.html)
|
||||
* [Enterprise Documentation Link](http://docs.alfresco.com/rm/concepts/welcome-rm.html)
|
||||
* [Contribution Model](../../CONTRIBUTING.md)
|
||||
|
||||
***
|
||||
|
||||
### Prerequisite Knowledge
|
||||
An understanding of Alfresco Content Services is assumed. The following pages from the [developer documentation](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services-Community-Edition/23.4/Alfresco-Content-Services-Community-Edition/Develop) give useful background information:
|
||||
An understanding of Alfresco Content Services is assumed. The following pages from the [developer documentation](http://docs.alfresco.com/5.2/concepts/dev-for-developers.html) give useful background information:
|
||||
|
||||
* [ACS Architecture](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Software-Architecture)
|
||||
* [Platform Extensions](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Extension-Points-Overview)
|
||||
* [Share Extensions](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Share-UI-Extension-Points)
|
||||
* [ACS Architecture](http://docs.alfresco.com/5.2/concepts/dev-arch-overview.html)
|
||||
* [Platform Extensions](http://docs.alfresco.com/5.2/concepts/dev-platform-extensions.html)
|
||||
* [Share Extensions](http://docs.alfresco.com/5.2/concepts/dev-extensions-share.html)
|
||||
|
||||
***
|
||||
|
||||
@@ -44,12 +44,12 @@ The RM Share module communicates with the repository module via REST APIs. Inter
|
||||
* A DAO layer responsible for CRUD operations against the database.
|
||||
|
||||
#### REST API
|
||||
The REST API endpoints fall into two main types - v0 (Webscripts) and v1. The [v0 API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/In-Process-Platform-Extension-Points/Web-Scripts) is older and not recommended for integrations. The [v1 API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/REST-API-Guide) is newer but isn't yet feature complete. If you are running RM locally then the GS API Explorer will be available at [this link](http://localhost:8080/gs-api-explorer/).
|
||||
The REST API endpoints fall into two main types - v0 (Webscripts) and v1. The [v0 API](http://docs.alfresco.com/5.2/references/dev-extension-points-webscripts.html) is older and not recommended for integrations. The [v1 API](http://docs.alfresco.com/5.1/pra/1/topics/pra-welcome-aara.html) is newer but isn't yet feature complete. If you are running RM locally then the GS API Explorer will be available at [this link](http://localhost:8080/gs-api-explorer/).
|
||||
|
||||
Internally the GS v1 REST API is built on the [Alfresco v1 REST API framework](https://community.alfresco.com/community/ecm/blog/2016/10/11/v1-rest-api-part-1-introduction). It aims to be consistent with this in terms of behaviour and naming.
|
||||
|
||||
#### Java Public API
|
||||
The Java service layer is fronted by a [Java Public API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Reference/Java-Foundation-API), which we will ensure backward compatible with previous releases. Before we remove any methods there will first be a release containing that method deprecated to allow third party integrations to migrate to a new method. The Java Public API also includes a set of POJO objects which are needed to communicate with the services. It is easy to identify classes that are part of the Java Public API as they are annotated `@AlfrescoPublicApi`.
|
||||
The Java service layer is fronted by a [Java Public API](http://docs.alfresco.com/5.2/concepts/java-public-api-list.html), which we will ensure backward compatible with previous releases. Before we remove any methods there will first be a release containing that method deprecated to allow third party integrations to migrate to a new method. The Java Public API also includes a set of POJO objects which are needed to communicate with the services. It is easy to identify classes that are part of the Java Public API as they are annotated `@AlfrescoPublicApi`.
|
||||
|
||||
Each Java service will have at least four beans defined for it:
|
||||
|
||||
@@ -61,7 +61,7 @@ Each Java service will have at least four beans defined for it:
|
||||
#### DAOs
|
||||
The DAOs are not part of the Java Public API, but handle CRUD operations against RM stored data. We have some custom queries to improve performance for particularly heavy operations.
|
||||
|
||||
We use standard Alfresco [data modelling](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/In-Process-Platform-Extension-Points/Content-Model-Extension-Point) to store RM metadata. We extend the [Alfresco patching mechanism](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/In-Process-Platform-Extension-Points/Patches) to provide community and enterprise schema upgrades.
|
||||
We use standard Alfresco [data modelling](http://docs.alfresco.com/5.2/references/dev-extension-points-content-model.html) to store RM metadata. We extend the [Alfresco patching mechanism](http://docs.alfresco.com/5.2/references/dev-extension-points-patch.html) to provide community and enterprise schema upgrades.
|
||||
|
||||
***
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
SOLR6_TAG=2.0.15
|
||||
SOLR6_TAG=2.0.14
|
||||
POSTGRES_TAG=16.6
|
||||
ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# Version label
|
||||
version.major=25
|
||||
version.minor=1
|
||||
version.revision=1
|
||||
version.revision=0
|
||||
version.label=
|
||||
|
||||
# Edition label
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-amps</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -23,6 +23,10 @@ function runAction(p_params)
|
||||
if (p_params.destNode.hasAspect("cm:lockable") && !p_params.destNode.hasAspect("trx:transferred"))
|
||||
{
|
||||
p_params.destNode.unlock();
|
||||
if(p_params.destNode.hasAspect("gd2:editingInGoogle"))
|
||||
{
|
||||
p_params.destNode.removeAspect("gd2:editingInGoogle");
|
||||
}
|
||||
}
|
||||
|
||||
var resultId = originalDoc.name,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
@@ -145,12 +145,6 @@
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>${dependency.awaitility.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2025 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2014 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -18,9 +18,6 @@
|
||||
*/
|
||||
package org.alfresco.util;
|
||||
|
||||
import static org.awaitility.Awaitility.await;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -29,20 +26,20 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Tests for our instance of {@link java.util.concurrent.ThreadPoolExecutor}
|
||||
*
|
||||
*
|
||||
* @author Nick Burch
|
||||
*/
|
||||
public class DynamicallySizedThreadPoolExecutorTest extends TestCase
|
||||
{
|
||||
|
||||
private static final Duration MAX_WAIT_TIMEOUT = Duration.ofSeconds(1);
|
||||
private static final Log logger = LogFactory.getLog(DynamicallySizedThreadPoolExecutorTest.class);
|
||||
private static Log logger = LogFactory.getLog(DynamicallySizedThreadPoolExecutorTest.class);
|
||||
private static final int DEFAULT_KEEP_ALIVE_TIME = 90;
|
||||
|
||||
@Override
|
||||
@@ -51,9 +48,9 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase
|
||||
SleepUntilAllWake.reset();
|
||||
}
|
||||
|
||||
public void testUpToCore()
|
||||
public void testUpToCore() throws Exception
|
||||
{
|
||||
DynamicallySizedThreadPoolExecutor exec = createInstance(5, 10, DEFAULT_KEEP_ALIVE_TIME);
|
||||
DynamicallySizedThreadPoolExecutor exec = createInstance(5,10, DEFAULT_KEEP_ALIVE_TIME);
|
||||
|
||||
assertEquals(0, exec.getPoolSize());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
@@ -64,15 +61,15 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase
|
||||
assertEquals(4, exec.getPoolSize());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(5, exec.getPoolSize());
|
||||
|
||||
|
||||
SleepUntilAllWake.wakeAll();
|
||||
waitForPoolSizeEquals(exec, 5);
|
||||
Thread.sleep(100);
|
||||
assertEquals(5, exec.getPoolSize());
|
||||
}
|
||||
|
||||
public void testPastCoreButNotHugeQueue()
|
||||
public void testPastCoreButNotHugeQueue() throws Exception
|
||||
{
|
||||
DynamicallySizedThreadPoolExecutor exec = createInstance(5, 10, DEFAULT_KEEP_ALIVE_TIME);
|
||||
DynamicallySizedThreadPoolExecutor exec = createInstance(5,10, DEFAULT_KEEP_ALIVE_TIME);
|
||||
|
||||
assertEquals(0, exec.getPoolSize());
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
@@ -83,7 +80,7 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(5, exec.getPoolSize());
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
|
||||
|
||||
// Need to hit max pool size before it adds more
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
@@ -92,20 +89,20 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(5, exec.getPoolSize());
|
||||
assertEquals(5, exec.getQueue().size());
|
||||
|
||||
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(5, exec.getPoolSize());
|
||||
assertEquals(7, exec.getQueue().size());
|
||||
|
||||
|
||||
SleepUntilAllWake.wakeAll();
|
||||
waitForPoolSizeEquals(exec, 5);
|
||||
Thread.sleep(100);
|
||||
assertEquals(5, exec.getPoolSize());
|
||||
}
|
||||
|
||||
|
||||
public void testToExpandQueue() throws Exception
|
||||
{
|
||||
DynamicallySizedThreadPoolExecutor exec = createInstance(2, 4, 5);
|
||||
DynamicallySizedThreadPoolExecutor exec = createInstance(2,4,1);
|
||||
|
||||
assertEquals(0, exec.getPoolSize());
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
@@ -113,39 +110,168 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(2, exec.getPoolSize());
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
|
||||
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(2, exec.getPoolSize());
|
||||
assertEquals(3, exec.getQueue().size());
|
||||
|
||||
|
||||
// Next should add one
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
waitForPoolSizeEquals(exec, 3); // Let the new thread spin up
|
||||
Thread.sleep(20); // Let the new thread spin up
|
||||
assertEquals(3, exec.getPoolSize());
|
||||
assertEquals(3, exec.getQueue().size());
|
||||
|
||||
// And again
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
waitForPoolSizeEquals(exec, 4); // Let the new thread spin up
|
||||
Thread.sleep(20); // Let the new thread spin up
|
||||
assertEquals(4, exec.getPoolSize());
|
||||
assertEquals(3, exec.getQueue().size());
|
||||
|
||||
|
||||
// But no more will be added, as we're at max
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(4, exec.getPoolSize());
|
||||
assertEquals(6, exec.getQueue().size());
|
||||
|
||||
|
||||
SleepUntilAllWake.wakeAll();
|
||||
Thread.sleep(100);
|
||||
|
||||
// All threads still running, as 5 second timeout
|
||||
|
||||
// All threads still running, as 1 second timeout
|
||||
assertEquals(4, exec.getPoolSize());
|
||||
}
|
||||
|
||||
public void offTestToExpandThenContract() throws Exception
|
||||
{
|
||||
DynamicallySizedThreadPoolExecutor exec = createInstance(2,4,1);
|
||||
exec.setKeepAliveTime(30, TimeUnit.MILLISECONDS);
|
||||
|
||||
assertEquals(0, exec.getPoolSize());
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(2, exec.getPoolSize());
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(2, exec.getPoolSize());
|
||||
assertEquals(3, exec.getQueue().size());
|
||||
|
||||
// Next should add one
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
Thread.sleep(20); // Let the new thread spin up
|
||||
assertEquals(3, exec.getPoolSize());
|
||||
assertEquals(3, exec.getQueue().size());
|
||||
|
||||
// And again
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
Thread.sleep(20); // Let the new thread spin up
|
||||
assertEquals(4, exec.getPoolSize());
|
||||
assertEquals(3, exec.getQueue().size());
|
||||
|
||||
// But no more will be added, as we're at max
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(4, exec.getPoolSize());
|
||||
assertEquals(6, exec.getQueue().size());
|
||||
|
||||
SleepUntilAllWake.wakeAll();
|
||||
Thread.sleep(100);
|
||||
|
||||
// Wait longer than the timeout without any work, which should
|
||||
// let all the extra threads go away
|
||||
// (Depending on how closely your JVM follows the specification,
|
||||
// we may fall back to the core size which is correct, or we
|
||||
// may go to zero which is wrong, but hey, it's the JVM...)
|
||||
logger.debug("Core pool size is " + exec.getCorePoolSize());
|
||||
logger.debug("Current pool size is " + exec.getPoolSize());
|
||||
logger.debug("Queue size is " + exec.getQueue().size());
|
||||
assertTrue(
|
||||
"Pool size should be 0-2 as everything is idle, was " + exec.getPoolSize(),
|
||||
exec.getPoolSize() >= 0
|
||||
);
|
||||
assertTrue(
|
||||
"Pool size should be 0-2 as everything is idle, was " + exec.getPoolSize(),
|
||||
exec.getPoolSize() <= 2
|
||||
);
|
||||
|
||||
SleepUntilAllWake.reset();
|
||||
|
||||
// Add 2 new jobs, will stay/ go to at 2 threads
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
|
||||
// Let the idle threads grab them, then check
|
||||
Thread.sleep(20);
|
||||
assertEquals(2, exec.getPoolSize());
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
|
||||
// 3 more, still at 2 threads
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
assertEquals(2, exec.getPoolSize());
|
||||
assertEquals(3, exec.getQueue().size());
|
||||
|
||||
// And again wait for it all
|
||||
SleepUntilAllWake.wakeAll();
|
||||
Thread.sleep(100);
|
||||
assertEquals(2, exec.getPoolSize());
|
||||
|
||||
|
||||
// Now decrease the overall pool size
|
||||
// Will rise and fall to there now
|
||||
exec.setCorePoolSize(1);
|
||||
|
||||
// Run a quick job, to ensure that the
|
||||
// "can I kill one yet" logic is applied
|
||||
SleepUntilAllWake.reset();
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
SleepUntilAllWake.wakeAll();
|
||||
|
||||
Thread.sleep(100);
|
||||
assertEquals(1, exec.getPoolSize());
|
||||
assertEquals(0, exec.getQueue().size());
|
||||
|
||||
SleepUntilAllWake.reset();
|
||||
|
||||
|
||||
// Push enough on to go up to 4 active threads
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
exec.execute(new SleepUntilAllWake());
|
||||
|
||||
Thread.sleep(20); // Let the new threads spin up
|
||||
assertEquals(4, exec.getPoolSize());
|
||||
assertEquals(6, exec.getQueue().size());
|
||||
|
||||
// Wait for them all to finish, should drop back to 1 now
|
||||
// (Or zero, if your JVM can't read the specification...)
|
||||
SleepUntilAllWake.wakeAll();
|
||||
Thread.sleep(100);
|
||||
assertTrue(
|
||||
"Pool size should be 0 or 1 as everything is idle, was " + exec.getPoolSize(),
|
||||
exec.getPoolSize() >= 0
|
||||
);
|
||||
assertTrue(
|
||||
"Pool size should be 0 or 1 as everything is idle, was " + exec.getPoolSize(),
|
||||
exec.getPoolSize() <= 1
|
||||
);
|
||||
}
|
||||
|
||||
private DynamicallySizedThreadPoolExecutor createInstance(int corePoolSize, int maximumPoolSize, int keepAliveTime)
|
||||
{
|
||||
// We need a thread factory
|
||||
@@ -165,11 +291,6 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase
|
||||
new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
}
|
||||
|
||||
private void waitForPoolSizeEquals(DynamicallySizedThreadPoolExecutor exec, int expectedSize)
|
||||
{
|
||||
await().atMost(MAX_WAIT_TIMEOUT).until(() -> exec.getPoolSize() == expectedSize);
|
||||
}
|
||||
|
||||
public static class SleepUntilAllWake implements Runnable
|
||||
{
|
||||
private static ConcurrentMap<String, Thread> sleeping = new ConcurrentHashMap<String, Thread>();
|
||||
@@ -178,33 +299,31 @@ public class DynamicallySizedThreadPoolExecutorTest extends TestCase
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (allAwake)
|
||||
return;
|
||||
|
||||
if(allAwake) return;
|
||||
|
||||
// Track us, and wait for the bang
|
||||
logger.debug("Adding thread: " + Thread.currentThread().getName());
|
||||
sleeping.put(Thread.currentThread().getName(), Thread.currentThread());
|
||||
try
|
||||
{
|
||||
Thread.sleep(30 * 1000);
|
||||
Thread.sleep(30*1000);
|
||||
System.err.println("Warning - Thread finished sleeping without wake!");
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
catch(InterruptedException e)
|
||||
{
|
||||
logger.debug("Interrupted thread: " + Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void wakeAll()
|
||||
{
|
||||
allAwake = true;
|
||||
for (Entry<String, Thread> t : sleeping.entrySet())
|
||||
for(Entry<String, Thread> t : sleeping.entrySet())
|
||||
{
|
||||
logger.debug("Interrupting thread: " + t.getKey());
|
||||
t.getValue().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset()
|
||||
{
|
||||
logger.debug("Resetting.");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2025 Alfresco Software Limited.
|
||||
* Copyright (C) 2005-2023 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
@@ -20,11 +20,13 @@ package org.alfresco.util.transaction;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
||||
import jakarta.transaction.RollbackException;
|
||||
import jakarta.transaction.Status;
|
||||
import jakarta.transaction.UserTransaction;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.springframework.transaction.CannotCreateTransactionException;
|
||||
import org.springframework.transaction.NoTransactionException;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
@@ -33,20 +35,21 @@ import org.springframework.transaction.support.AbstractPlatformTransactionManage
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
|
||||
/**
|
||||
* @author Derek Hulley
|
||||
* @see org.alfresco.util.transaction.SpringAwareUserTransaction
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class SpringAwareUserTransactionTest extends TestCase
|
||||
{
|
||||
private DummyTransactionManager transactionManager;
|
||||
private FailingTransactionManager failingTransactionManager;
|
||||
private UserTransaction txn;
|
||||
|
||||
|
||||
public SpringAwareUserTransactionTest()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception
|
||||
{
|
||||
@@ -54,7 +57,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
failingTransactionManager = new FailingTransactionManager();
|
||||
txn = getTxn();
|
||||
}
|
||||
|
||||
|
||||
private UserTransaction getTxn()
|
||||
{
|
||||
return new SpringAwareUserTransaction(
|
||||
@@ -64,13 +67,13 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
TransactionDefinition.PROPAGATION_REQUIRED,
|
||||
TransactionDefinition.TIMEOUT_DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
public void testSetUp() throws Exception
|
||||
{
|
||||
assertNotNull(transactionManager);
|
||||
assertNotNull(txn);
|
||||
}
|
||||
|
||||
|
||||
private void checkNoStatusOnThread()
|
||||
{
|
||||
try
|
||||
@@ -83,7 +86,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testNoTxnStatus() throws Exception
|
||||
{
|
||||
checkNoStatusOnThread();
|
||||
@@ -131,7 +134,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
}
|
||||
checkNoStatusOnThread();
|
||||
}
|
||||
|
||||
|
||||
public void testSimpleTxnWithRollback() throws Exception
|
||||
{
|
||||
testNoTxnStatus();
|
||||
@@ -153,7 +156,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
transactionManager.getStatus());
|
||||
checkNoStatusOnThread();
|
||||
}
|
||||
|
||||
|
||||
public void testNoBeginCommit() throws Exception
|
||||
{
|
||||
testNoTxnStatus();
|
||||
@@ -168,7 +171,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
}
|
||||
checkNoStatusOnThread();
|
||||
}
|
||||
|
||||
|
||||
public void testPostRollbackCommitDetection() throws Exception
|
||||
{
|
||||
testNoTxnStatus();
|
||||
@@ -186,7 +189,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
}
|
||||
checkNoStatusOnThread();
|
||||
}
|
||||
|
||||
|
||||
public void testPostSetRollbackOnlyCommitDetection() throws Exception
|
||||
{
|
||||
testNoTxnStatus();
|
||||
@@ -205,7 +208,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
}
|
||||
checkNoStatusOnThread();
|
||||
}
|
||||
|
||||
|
||||
public void testMismatchedBeginCommit() throws Exception
|
||||
{
|
||||
UserTransaction txn1 = getTxn();
|
||||
@@ -215,18 +218,18 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
|
||||
txn1.begin();
|
||||
txn2.begin();
|
||||
|
||||
|
||||
txn2.commit();
|
||||
txn1.commit();
|
||||
|
||||
|
||||
checkNoStatusOnThread();
|
||||
|
||||
|
||||
txn1 = getTxn();
|
||||
txn2 = getTxn();
|
||||
|
||||
|
||||
txn1.begin();
|
||||
txn2.begin();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
txn1.commit();
|
||||
@@ -242,6 +245,58 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
checkNoStatusOnThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for leaked transactions (no guarantee it will succeed due to reliance
|
||||
* on garbage collector), so disabled by default.
|
||||
*
|
||||
* Also, if it succeeds, transaction call stack tracing will be enabled
|
||||
* potentially hitting the performance of all subsequent tests.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void xtestLeakedTransactionLogging() throws Exception
|
||||
{
|
||||
assertFalse(SpringAwareUserTransaction.isCallStackTraced());
|
||||
|
||||
TrxThread t1 = new TrxThread();
|
||||
t1.start();
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
|
||||
TrxThread t2 = new TrxThread();
|
||||
t2.start();
|
||||
System.gc();
|
||||
Thread.sleep(1000);
|
||||
|
||||
assertTrue(SpringAwareUserTransaction.isCallStackTraced());
|
||||
|
||||
TrxThread t3 = new TrxThread();
|
||||
t3.start();
|
||||
System.gc();
|
||||
Thread.sleep(3000);
|
||||
System.gc();
|
||||
Thread.sleep(3000);
|
||||
}
|
||||
|
||||
private class TrxThread extends Thread
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
getTrx();
|
||||
}
|
||||
catch (Exception e) {}
|
||||
}
|
||||
|
||||
public void getTrx() throws Exception
|
||||
{
|
||||
UserTransaction txn = getTxn();
|
||||
txn.begin();
|
||||
txn = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void testConnectionPoolException() throws Exception
|
||||
{
|
||||
testNoTxnStatus();
|
||||
@@ -256,7 +311,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
// Expected fail
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private UserTransaction getFailingTxn()
|
||||
{
|
||||
return new SpringAwareUserTransaction(
|
||||
@@ -266,7 +321,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
TransactionDefinition.PROPAGATION_REQUIRED,
|
||||
TransactionDefinition.TIMEOUT_DEFAULT);
|
||||
}
|
||||
|
||||
|
||||
public void testTransactionListenerOrder() throws Throwable
|
||||
{
|
||||
testNoTxnStatus();
|
||||
@@ -305,12 +360,12 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
}
|
||||
checkNoStatusOnThread();
|
||||
}
|
||||
|
||||
|
||||
private static class TestTransactionListener extends TransactionListenerAdapter
|
||||
{
|
||||
private final String name;
|
||||
private final StringBuffer buffer;
|
||||
|
||||
|
||||
public TestTransactionListener(String name, StringBuffer buffer)
|
||||
{
|
||||
Objects.requireNonNull(name);
|
||||
@@ -318,18 +373,18 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
this.name = name;
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly)
|
||||
{
|
||||
buffer.append(name);
|
||||
}
|
||||
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
@@ -339,17 +394,17 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return name.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to check that the transaction manager is being called correctly
|
||||
*
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@@ -357,7 +412,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
{
|
||||
private int status = Status.STATUS_NO_TRANSACTION;
|
||||
private Object txn = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns one of the {@link Status Status.STATUS_XXX} constants
|
||||
*/
|
||||
@@ -386,10 +441,10 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
status = Status.STATUS_ROLLEDBACK;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Throws {@link NoSuchElementException} on begin()
|
||||
*
|
||||
*
|
||||
* @author alex.mukha
|
||||
*/
|
||||
private static class FailingTransactionManager extends AbstractPlatformTransactionManager
|
||||
@@ -397,7 +452,7 @@ public class SpringAwareUserTransactionTest extends TestCase
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int status = Status.STATUS_NO_TRANSACTION;
|
||||
private Object txn = new Object();
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns one of the {@link Status Status.STATUS_XXX} constants
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
</project>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# More infos about this image: https://github.com/Alfresco/alfresco-docker-base-tomcat
|
||||
FROM alfresco/alfresco-base-tomcat:tomcat10-jre17-rockylinux9@sha256:9622418e142fb4fe1c5320666ad61ea292bc5c98f3dd0b550b6add33d18f659f
|
||||
FROM alfresco/alfresco-base-tomcat:tomcat10-jre17-rockylinux9@sha256:395664f9d9be0c9f73d3b722a58fd559ee7231609b263dfe19502617652740e3
|
||||
|
||||
# Set default docker_context.
|
||||
ARG resource_path=target
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
SOLR6_TAG=2.0.15
|
||||
SOLR6_TAG=2.0.14
|
||||
POSTGRES_TAG=16.6
|
||||
ACTIVEMQ_TAG=5.18.3-jre17-rockylinux8
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
## Synopsis
|
||||
|
||||
**TAS**( **T**est **A**utomation **S**ystem)- **CMIS** is the project that handles the automated tests related only to CMIS API integrated with Alfresco One [Alfresco CMIS API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/Reference/CMIS-API).
|
||||
**TAS**( **T**est **A**utomation **S**ystem)- **CMIS** is the project that handles the automated tests related only to CMIS API integrated with Alfresco One [Alfresco CMIS API](http://docs.alfresco.com/5.1/pra/1/topics/cmis-welcome.html).
|
||||
|
||||
It is based on Apache Maven, compatible with major IDEs and is using also Spring capabilities for dependency injection.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<organization>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
||||
@@ -27,7 +27,7 @@ Back to [TAS Master Documentation](https://git.alfresco.com/tas/alfresco-tas-uti
|
||||
|
||||
## Synopsis
|
||||
|
||||
**TAS**( **T**est **A**utomation **S**ystem)- **RESTAPI** is the project that handles the automated tests related only to [Alfresco REST API](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/REST-API-Guide).
|
||||
**TAS**( **T**est **A**utomation **S**ystem)- **RESTAPI** is the project that handles the automated tests related only to [Alfresco REST API](http://docs.alfresco.com/5.1/pra/1/topics/pra-welcome.html).
|
||||
|
||||
It is based on Apache Maven, compatible with major IDEs and is using also Spring capabilities for dependency injection.
|
||||
|
||||
@@ -271,7 +271,7 @@ restClient.onResponse().assertThat().body("entry.modifiedBy.firstName", org.hamc
|
||||
|
||||
### How to generate models or check coverage
|
||||
|
||||
There are some simple generators that could parse [Swagger YAML](https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Develop/REST-API-Guide/Things-to-Know-Before-You-Start/The-API-Explorer-is-Your-Source-of-Truth) files and provide some usefull information to you like:
|
||||
There are some simple generators that could parse [Swagger YAML](http://docs.alfresco.com/community/concepts/alfresco-sdk-tutorials-using-rest-api-explorer.html) files and provide some usefull information to you like:
|
||||
|
||||
a) Show on screen the actual coverage of TAS vs requests that exists in each YAML file - defined in pom.xml)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*-
|
||||
* #%L
|
||||
* alfresco-tas-restapi
|
||||
* %%
|
||||
* 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.rest.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.alfresco.utility.model.TestModel;
|
||||
|
||||
/**
|
||||
* Authorization code implementation
|
||||
*/
|
||||
public class RestAuthCodeModel extends TestModel
|
||||
{
|
||||
@JsonProperty
|
||||
private String authorizationCode;
|
||||
|
||||
public String getAuthorizationCode()
|
||||
{
|
||||
return authorizationCode;
|
||||
}
|
||||
|
||||
public void setAuthorizationCode(String authorizationCode)
|
||||
{
|
||||
this.authorizationCode = authorizationCode;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*-
|
||||
* #%L
|
||||
* alfresco-tas-restapi
|
||||
* %%
|
||||
* 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.rest.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import org.alfresco.utility.model.TestModel;
|
||||
|
||||
/**
|
||||
* Authorization key implementation
|
||||
*/
|
||||
public class RestAuthKeyModel extends TestModel
|
||||
{
|
||||
@JsonProperty(required = true)
|
||||
private String authorizationKey;
|
||||
|
||||
public String getAuthorizationKey()
|
||||
{
|
||||
return authorizationKey;
|
||||
}
|
||||
|
||||
public void setAuthorizationKey(String authorizationKey)
|
||||
{
|
||||
this.authorizationKey = authorizationKey;
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,6 @@ import org.alfresco.rest.core.RestWrapper;
|
||||
import org.alfresco.rest.exception.EmptyJsonResponseException;
|
||||
import org.alfresco.rest.exception.JsonToModelConversionException;
|
||||
import org.alfresco.rest.model.RestActivityModelsCollection;
|
||||
import org.alfresco.rest.model.RestAuthCodeModel;
|
||||
import org.alfresco.rest.model.RestAuthKeyModel;
|
||||
import org.alfresco.rest.model.RestFavoriteSiteModel;
|
||||
import org.alfresco.rest.model.RestGroupsModelsCollection;
|
||||
import org.alfresco.rest.model.RestNetworkModel;
|
||||
@@ -448,31 +446,6 @@ public class People extends ModelRequest<People>
|
||||
restWrapper.processEmptyModel(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reauthorizes a user.
|
||||
*/
|
||||
public void reauthorizeUser(RestAuthKeyModel authKey)
|
||||
{
|
||||
var request = RestRequest.requestWithBody(HttpMethod.POST, authKey.toJson(), "people/{personId}/reauthorize", this.person.getUsername());
|
||||
restWrapper.processEmptyModel(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reauthorization code.
|
||||
*/
|
||||
public RestAuthCodeModel getReauthorizationCode()
|
||||
{
|
||||
var request = RestRequest.simpleRequest(HttpMethod.POST, "people/{personId}/reauthorization-code", this.person.getUsername());
|
||||
try
|
||||
{
|
||||
return restWrapper.processModel(RestAuthCodeModel.class, request);
|
||||
}
|
||||
catch (JsonToModelConversionException | EmptyJsonResponseException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update avatar image PUT call on 'people/{nodeId}/children
|
||||
*/
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package org.alfresco.rest.people.deauthorization.community;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import org.alfresco.rest.RestTest;
|
||||
import org.alfresco.rest.model.RestAuthKeyModel;
|
||||
import org.alfresco.utility.model.TestGroup;
|
||||
import org.alfresco.utility.model.UserModel;
|
||||
import org.alfresco.utility.testrail.ExecutionType;
|
||||
import org.alfresco.utility.testrail.annotation.TestRail;
|
||||
|
||||
/**
|
||||
* Verifies API behavior in community edition. Should be excluded in enterprise edition.
|
||||
*/
|
||||
@Test
|
||||
public class ReauthorizeSanityTests extends RestTest
|
||||
{
|
||||
private UserModel userModel;
|
||||
private UserModel adminUser;
|
||||
|
||||
@BeforeClass(alwaysRun = true)
|
||||
public void dataPreparation()
|
||||
{
|
||||
adminUser = dataUser.getAdminUser();
|
||||
userModel = dataUser.createRandomTestUser();
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.PEOPLE, TestGroup.SANITY})
|
||||
@TestRail(section = {TestGroup.REST_API, TestGroup.PEOPLE}, executionType = ExecutionType.SANITY,
|
||||
description = "Check if reauthorization is not implemented in Community Edition")
|
||||
public void reauthorizationIsNotImplementedInCommunityEdition()
|
||||
{
|
||||
// given
|
||||
var key = new RestAuthKeyModel();
|
||||
key.setAuthorizationKey("am9obnRlc3RAMTIzNDU=");
|
||||
|
||||
// when admin invokes API
|
||||
restClient.authenticateUser(adminUser).withCoreAPI().usingUser(userModel).reauthorizeUser(key);
|
||||
// then
|
||||
restClient.assertStatusCodeIs(HttpStatus.NOT_IMPLEMENTED);
|
||||
|
||||
// when user invokes API
|
||||
restClient.authenticateUser(userModel).withCoreAPI().usingUser(userModel).reauthorizeUser(key);
|
||||
// then
|
||||
restClient.assertStatusCodeIs(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
@Test(groups = {TestGroup.REST_API, TestGroup.PEOPLE, TestGroup.SANITY})
|
||||
@TestRail(section = {TestGroup.REST_API, TestGroup.PEOPLE}, executionType = ExecutionType.SANITY,
|
||||
description = "Check if the reauthorization code is not implemented in Community Edition")
|
||||
public void reauthorizationCodeIsNotImplementedInCommunityEdition()
|
||||
{
|
||||
// when admin invokes API
|
||||
restClient.authenticateUser(adminUser).withCoreAPI().usingUser(userModel).getReauthorizationCode();
|
||||
// then
|
||||
restClient.assertStatusCodeIs(HttpStatus.NOT_IMPLEMENTED);
|
||||
|
||||
// when user invokes API
|
||||
restClient.authenticateUser(userModel).withCoreAPI().usingUser(userModel).getReauthorizationCode();
|
||||
// then
|
||||
restClient.assertStatusCodeIs(HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<developers>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -74,7 +74,7 @@ ModuleDetails shareServicesModule = moduleService.getModule("alfresco-share-serv
|
||||
<div class="index-list">
|
||||
<h4><%=descriptorService.getServerDescriptor().getEdition()%></h4>
|
||||
<p></p>
|
||||
<p><a href="https://support.hyland.com/p/alfresco">Online Documentation</a></p>
|
||||
<p><a href="http://docs.alfresco.com/">Online Documentation</a></p>
|
||||
<p></p>
|
||||
<%
|
||||
if (shareServicesModule != null && ModuleInstallState.INSTALLED.equals(shareServicesModule.getInstallState()))
|
||||
|
||||
22
pom.xml
22
pom.xml
@@ -2,7 +2,7 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Alfresco Community Repo Parent</name>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<properties>
|
||||
<acs.version.major>25</acs.version.major>
|
||||
<acs.version.minor>1</acs.version.minor>
|
||||
<acs.version.revision>2</acs.version.revision>
|
||||
<acs.version.revision>0</acs.version.revision>
|
||||
<acs.version.label />
|
||||
<amp.min.version>${acs.version.major}.0.0</amp.min.version>
|
||||
|
||||
@@ -48,17 +48,17 @@
|
||||
<dependency.alfresco-hb-data-sender.version>1.1.1</dependency.alfresco-hb-data-sender.version>
|
||||
<dependency.alfresco-trashcan-cleaner.version>2.4.2</dependency.alfresco-trashcan-cleaner.version>
|
||||
<dependency.alfresco-jlan.version>7.5</dependency.alfresco-jlan.version>
|
||||
<dependency.alfresco-server-root.version>7.0.2</dependency.alfresco-server-root.version>
|
||||
<dependency.alfresco-server-root.version>7.0.1</dependency.alfresco-server-root.version>
|
||||
<dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version>
|
||||
<dependency.activiti.version>5.23.0</dependency.activiti.version>
|
||||
<dependency.alfresco-transform-core.version>5.1.7</dependency.alfresco-transform-core.version>
|
||||
<dependency.alfresco-transform-service.version>4.1.7</dependency.alfresco-transform-service.version>
|
||||
<dependency.alfresco-transform-core.version>5.1.6</dependency.alfresco-transform-core.version>
|
||||
<dependency.alfresco-transform-service.version>4.1.6</dependency.alfresco-transform-service.version>
|
||||
<dependency.alfresco-greenmail.version>7.1</dependency.alfresco-greenmail.version>
|
||||
<dependency.acs-event-model.version>1.0.2</dependency.acs-event-model.version>
|
||||
|
||||
<dependency.aspectj.version>1.9.22.1</dependency.aspectj.version>
|
||||
<dependency.spring.version>6.2.2</dependency.spring.version>
|
||||
<dependency.spring-security.version>6.3.7</dependency.spring-security.version>
|
||||
<dependency.spring.version>6.2.1</dependency.spring.version>
|
||||
<dependency.spring-security.version>6.3.4</dependency.spring-security.version>
|
||||
<dependency.antlr.version>3.5.3</dependency.antlr.version>
|
||||
<dependency.jackson.version>2.17.2</dependency.jackson.version>
|
||||
<dependency.cxf.version>4.1.0</dependency.cxf.version>
|
||||
@@ -86,7 +86,7 @@
|
||||
<dependency.poi.version>5.3.0</dependency.poi.version>
|
||||
<dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version>
|
||||
<dependency.camel.version>4.6.0</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
|
||||
<dependency.netty.version>4.1.118.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
|
||||
<dependency.netty.version>4.1.117.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
|
||||
<dependency.activemq.version>5.18.6</dependency.activemq.version>
|
||||
<dependency.apache-compress.version>1.27.1</dependency.apache-compress.version>
|
||||
<dependency.awaitility.version>4.2.2</dependency.awaitility.version>
|
||||
@@ -112,10 +112,10 @@
|
||||
<dependency.jakarta-ee-json-api.version>2.1.3</dependency.jakarta-ee-json-api.version>
|
||||
<dependency.jakarta-ee-json-impl.version>1.1.7</dependency.jakarta-ee-json-impl.version>
|
||||
<dependency.jakarta-json-path.version>2.9.0</dependency.jakarta-json-path.version>
|
||||
<dependency.json-smart.version>2.5.2</dependency.json-smart.version>
|
||||
<dependency.json-smart.version>2.5.1</dependency.json-smart.version>
|
||||
<alfresco.googledrive.version>4.1.0</alfresco.googledrive.version>
|
||||
<alfresco.aos-module.version>3.2.0</alfresco.aos-module.version>
|
||||
<alfresco.api-explorer.version>25.1.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
|
||||
<alfresco.api-explorer.version>25.1.0-A1</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
|
||||
|
||||
<alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
|
||||
<license-maven-plugin.version>2.4.0</license-maven-plugin.version>
|
||||
@@ -410,7 +410,7 @@
|
||||
<dependency>
|
||||
<groupId>commons-beanutils</groupId>
|
||||
<artifactId>commons-beanutils</artifactId>
|
||||
<version>1.11.0</version>
|
||||
<version>1.9.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-codec</groupId>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* 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.rest.api.model;
|
||||
|
||||
/**
|
||||
* An object representing user authorization code.
|
||||
*/
|
||||
public record AuthCode(String authorizationCode)
|
||||
{}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* 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.rest.api.model;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.length;
|
||||
|
||||
/**
|
||||
* An object representing user authorization key request body.
|
||||
*/
|
||||
public record AuthKey(String authorizationKey)
|
||||
{
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
// for security reasons the key content should be never logged
|
||||
return "AuthKey[" +
|
||||
"authorizationKeyLength=" + length(authorizationKey) +
|
||||
']';
|
||||
}
|
||||
}
|
||||
@@ -34,8 +34,6 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.rest.api.People;
|
||||
import org.alfresco.rest.api.model.AuthCode;
|
||||
import org.alfresco.rest.api.model.AuthKey;
|
||||
import org.alfresco.rest.api.model.Client;
|
||||
import org.alfresco.rest.api.model.PasswordReset;
|
||||
import org.alfresco.rest.api.model.Person;
|
||||
@@ -247,29 +245,4 @@ public class PeopleEntityResource implements EntityResourceAction.ReadById<Perso
|
||||
{
|
||||
// functionality is not implemented in community edition
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the authorization code.
|
||||
*
|
||||
* Not supported in community edition.
|
||||
*/
|
||||
@Operation("reauthorization-code")
|
||||
@WebApiDescription(title = "Get the reauthorization code", description = "Get the reauthorization code", successStatus = HttpServletResponse.SC_NOT_IMPLEMENTED)
|
||||
public AuthCode getReauthorizationCode(String personId, Void body, Parameters parameters, WithResponse withResponse)
|
||||
{
|
||||
// functionality is not implemented in community edition
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reauthorize user.
|
||||
*
|
||||
* Not supported in community edition.
|
||||
*/
|
||||
@Operation("reauthorize")
|
||||
@WebApiDescription(title = "Reauthorize user", description = "Performs user reauthorization", successStatus = HttpServletResponse.SC_NOT_IMPLEMENTED)
|
||||
public void reauthorizeUser(String personId, AuthKey authKey, Parameters parameters, WithResponse withResponse)
|
||||
{
|
||||
// functionality is not implemented in community edition
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Admin Console
|
||||
admin-console.help=Help
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Successfully saved values.
|
||||
|
||||
admin-console.host=Host
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Konzole pro spr\u00e1vce
|
||||
admin-console.help=N\u00e1pov\u011bda
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Hodnoty byly \u00fasp\u011b\u0161n\u011b ulo\u017eeny.
|
||||
|
||||
admin-console.host=Hostitel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Administrationskonsol
|
||||
admin-console.help=Hj\u00e6lp
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=V\u00e6rdierne blev gemt.
|
||||
|
||||
admin-console.host=V\u00e6rt
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Administratorkonsole
|
||||
admin-console.help=Hilfe
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Erfolgreich gespeicherte Werte.
|
||||
|
||||
admin-console.host=Host
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Consola de administraci\u00f3n
|
||||
admin-console.help=Ayuda
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Valores guardados correctamente.
|
||||
|
||||
admin-console.host=Host
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Hallintakonsoli
|
||||
admin-console.help=Ohje
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Arvot tallennettiin.
|
||||
|
||||
admin-console.host=Is\u00e4nt\u00e4
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Console d'administration
|
||||
admin-console.help=Aide
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Les valeurs ont bien \u00e9t\u00e9 enregistr\u00e9es.
|
||||
|
||||
admin-console.host=H\u00f4te
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Console di amministrazione
|
||||
admin-console.help=Aiuto
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=I valori sono stati salvati.
|
||||
|
||||
admin-console.host=Host
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=\u7ba1\u7406\u30b3\u30f3\u30bd\u30fc\u30eb
|
||||
admin-console.help=\u30d8\u30eb\u30d7
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=\u5024\u3092\u6b63\u5e38\u306b\u4fdd\u5b58\u3057\u307e\u3057\u305f\u3002
|
||||
|
||||
admin-console.host=\u30db\u30b9\u30c8
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Admin-konsoll
|
||||
admin-console.help=Hjelp
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Verdier som ble lagret.
|
||||
|
||||
admin-console.host=Vert
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Beheerconsole
|
||||
admin-console.help=Help
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Waarden zijn opgeslagen.
|
||||
|
||||
admin-console.host=Host
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Konsola administracyjna
|
||||
admin-console.help=Pomoc
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Warto\u015bci zosta\u0142y zapisane pomy\u015blnie.
|
||||
|
||||
admin-console.host=Host
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Console de administra\u00e7\u00e3o
|
||||
admin-console.help=Ajuda
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=Valores salvos com sucesso.
|
||||
|
||||
admin-console.host=Host
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=\u041a\u043e\u043d\u0441\u043e\u043b\u044c \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0430
|
||||
admin-console.help=\u0421\u043f\u0440\u0430\u0432\u043a\u0430
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f.
|
||||
|
||||
admin-console.host=\u0425\u043e\u0441\u0442
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=Admin-konsol
|
||||
admin-console.help=Hj\u00e4lp
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=V\u00e4rden sparades.
|
||||
|
||||
admin-console.host=V\u00e4rd
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# I18N messages for the Repository Admin Console
|
||||
admin-console.header=\u7ba1\u7406\u63a7\u5236\u53f0
|
||||
admin-console.help=\u5e2e\u52a9
|
||||
admin-console.help-link=https://support.hyland.com/p/alfresco
|
||||
admin-console.help-link=http://docs.alfresco.com/{0}/concepts/ch-administering.html
|
||||
admin-console.success=\u5df2\u6210\u529f\u4fdd\u5b58\u7684\u503c\u3002
|
||||
|
||||
admin-console.host=\u4e3b\u673a
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
<#macro page title readonly=false controller=DEFAULT_CONTROLLER!"/admin" params="" dialog=false>
|
||||
<#assign FORM_ID="admin-jmx-form" />
|
||||
<#if server.edition == "Community">
|
||||
<#assign docsEdition = "/Alfresco-Content-Services-Community-Edition/" + server.getVersionMajor() + "." + server.getVersionMinor() + "/Alfresco-Content-Services-Community-Edition" />
|
||||
<#assign docsEdition = "community" />
|
||||
<#elseif server.edition == "Enterprise" >
|
||||
<#assign docsEdition = "/Alfresco-Content-Services/" + server.getVersionMajor() + "." + server.getVersionMinor() + "/Alfresco-Content-Services" />
|
||||
<#assign docsEdition = server.getVersionMajor() + "." + server.getVersionMinor() />
|
||||
</#if>
|
||||
<#if metadata??>
|
||||
<#assign HOSTNAME>${msg("admin-console.host")}: ${metadata.hostname}</#assign>
|
||||
@@ -551,7 +551,7 @@ Admin.addEventListener(window, 'load', function() {
|
||||
Template for a full page view
|
||||
-->
|
||||
<div class="sticky-wrapper">
|
||||
|
||||
|
||||
<div class="header">
|
||||
<span><a href="${url.serviceContext}${DEFAULT_CONTROLLER!"/admin"}">${msg("admin-console.header")}</a></span><#if metadata??><span class="meta">${HOSTNAME}</span><span class="meta">${HOSTADDR}</span></#if>
|
||||
<div style="float:right"><a href="${msg("admin-console.help-link", docsEdition)}" target="_blank">${msg("admin-console.help")}</a></div>
|
||||
@@ -908,4 +908,4 @@ Admin.addEventListener(window, 'load', function() {
|
||||
<#macro button label description="" onclick="" style="" id="" class="" disabled="false">
|
||||
<input class="<#if class?has_content>${class?html}<#else>inline</#if>" <#if id?has_content>id="${id?html}"</#if> <#if style?has_content>style="${style?html}"</#if> type="button" value="${label?html}" onclick="${onclick?html}" <#if disabled="true">disabled="true"</#if> />
|
||||
<#if description?has_content><span class="description">${description?html}</span></#if>
|
||||
</#macro>
|
||||
</#macro>
|
||||
@@ -27,7 +27,7 @@ to integrate with a number of external Authentication providers including
|
||||
* https://github.com/Alfresco/alfresco-data-model/tree/master/src/main/java/org/alfresco/repo/security/authentication
|
||||
* License: LGPL
|
||||
* Issue Tracker Link: https://issues.alfresco.com/jira/issues/?jql=project%3DREPO
|
||||
* Documentation Link: https://support.hyland.com/r/Alfresco/Alfresco-Content-Services-Community-Edition/23.4/Alfresco-Content-Services-Community-Edition/Administer/Manage-Security/Authentication-and-sync
|
||||
* Documentation Link: http://docs.alfresco.com/5.2/concepts/auth-intro.html
|
||||
* Contribution Model: Alfresco Open Source
|
||||
***
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
* Source Code Link:m https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/
|
||||
* License: LGPL
|
||||
* Issue Tracker Link: https://issues.alfresco.com/jira/secure/RapidBoard.jspa?projectKey=REPO&useStoredSettings=true&rapidView=379
|
||||
* Documentation Link: https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Configure/Repository/About-Versioning
|
||||
* Documentation Link: http://docs.alfresco.com/5.1/concepts/versioning.html
|
||||
* Contribution Model: Alfresco publishes the source code and will review proposed patch requests
|
||||
***
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-community-repo</artifactId>
|
||||
<version>25.1.2.6-SNAPSHOT</version>
|
||||
<version>25.1.0.46-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -2,96 +2,93 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.action.executer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.action.ParameterDefinitionImpl;
|
||||
import org.alfresco.repo.action.access.ActionAccessRestriction;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.action.Action;
|
||||
import org.alfresco.service.cmr.action.ParameterDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
|
||||
/**
|
||||
* Add features action executor implementation.
|
||||
*
|
||||
* @author Roy Wetherall
|
||||
*/
|
||||
public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase
|
||||
{
|
||||
/**
|
||||
* Action constants
|
||||
*/
|
||||
public static final String NAME = "add-features";
|
||||
public static final String PARAM_ASPECT_NAME = "aspect-name";
|
||||
public static final String PARAM_CONSTRAINT = "ac-aspects";
|
||||
|
||||
/**
|
||||
* The node service
|
||||
*/
|
||||
private NodeService nodeService;
|
||||
|
||||
/** Transaction Service, used for retrying operations */
|
||||
private TransactionService transactionService;
|
||||
|
||||
/**
|
||||
* Set the node service
|
||||
*
|
||||
* @param nodeService
|
||||
* the node service
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction service
|
||||
*
|
||||
* @param transactionService
|
||||
* the transaction service
|
||||
*/
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adhoc properties are allowed for this executor
|
||||
*/
|
||||
@Override
|
||||
protected boolean getAdhocPropertiesAllowed()
|
||||
{
|
||||
return true;
|
||||
package org.alfresco.repo.action.executer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.action.ParameterDefinitionImpl;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||
import org.alfresco.service.cmr.action.Action;
|
||||
import org.alfresco.service.cmr.action.ParameterDefinition;
|
||||
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
|
||||
/**
|
||||
* Add features action executor implementation.
|
||||
*
|
||||
* @author Roy Wetherall
|
||||
*/
|
||||
public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase
|
||||
{
|
||||
/**
|
||||
* Action constants
|
||||
*/
|
||||
public static final String NAME = "add-features";
|
||||
public static final String PARAM_ASPECT_NAME = "aspect-name";
|
||||
public static final String PARAM_CONSTRAINT = "ac-aspects";
|
||||
|
||||
/**
|
||||
* The node service
|
||||
*/
|
||||
private NodeService nodeService;
|
||||
|
||||
/** Transaction Service, used for retrying operations */
|
||||
private TransactionService transactionService;
|
||||
|
||||
/**
|
||||
* Set the node service
|
||||
*
|
||||
* @param nodeService the node service
|
||||
*/
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction service
|
||||
*
|
||||
* @param transactionService the transaction service
|
||||
*/
|
||||
public void setTransactionService(TransactionService transactionService)
|
||||
{
|
||||
this.transactionService = transactionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adhoc properties are allowed for this executor
|
||||
*/
|
||||
@Override
|
||||
protected boolean getAdhocPropertiesAllowed()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,61 +96,55 @@ public class AddFeaturesActionExecuter extends ActionExecuterAbstractBase
|
||||
*/
|
||||
public void executeImpl(final Action ruleAction, final NodeRef actionedUponNodeRef)
|
||||
{
|
||||
if (this.nodeService.exists(actionedUponNodeRef))
|
||||
{
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Void>() {
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
|
||||
QName aspectQName = null;
|
||||
|
||||
if (!nodeService.exists(actionedUponNodeRef))
|
||||
{
|
||||
// Node has gone away, skip
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build the aspect details
|
||||
Map<String, Serializable> paramValues = ruleAction.getParameterValues();
|
||||
removeActionContextParameter(paramValues);
|
||||
for (Map.Entry<String, Serializable> entry : paramValues.entrySet())
|
||||
{
|
||||
if (entry.getKey().equals(PARAM_ASPECT_NAME) == true)
|
||||
{
|
||||
aspectQName = (QName) entry.getValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must be an adhoc property
|
||||
QName propertyQName = QName.createQName(entry.getKey());
|
||||
Serializable propertyValue = entry.getValue();
|
||||
properties.put(propertyQName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the aspect
|
||||
nodeService.addAspect(actionedUponNodeRef, aspectQName, properties);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
|
||||
{
|
||||
paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT_NAME), false, "ac-aspects"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove actionContext from the parameter values to declassify as an adhoc property
|
||||
*/
|
||||
private void removeActionContextParameter(Map<String, Serializable> paramValues)
|
||||
{
|
||||
paramValues.remove(ActionAccessRestriction.ACTION_CONTEXT_PARAM_NAME);
|
||||
}
|
||||
}
|
||||
if (this.nodeService.exists(actionedUponNodeRef))
|
||||
{
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionCallback<Void>()
|
||||
{
|
||||
public Void execute() throws Throwable
|
||||
{
|
||||
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
|
||||
QName aspectQName = null;
|
||||
|
||||
if(! nodeService.exists(actionedUponNodeRef))
|
||||
{
|
||||
// Node has gone away, skip
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build the aspect details
|
||||
Map<String, Serializable> paramValues = ruleAction.getParameterValues();
|
||||
for (Map.Entry<String, Serializable> entry : paramValues.entrySet())
|
||||
{
|
||||
if (entry.getKey().equals(PARAM_ASPECT_NAME) == true)
|
||||
{
|
||||
aspectQName = (QName)entry.getValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Must be an adhoc property
|
||||
QName propertyQName = QName.createQName(entry.getKey());
|
||||
Serializable propertyValue = entry.getValue();
|
||||
properties.put(propertyQName, propertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the aspect
|
||||
nodeService.addAspect(actionedUponNodeRef, aspectQName, properties);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
|
||||
*/
|
||||
@Override
|
||||
protected void addParameterDefinitions(List<ParameterDefinition> paramList)
|
||||
{
|
||||
paramList.add(new ParameterDefinitionImpl(PARAM_ASPECT_NAME, DataTypeDefinition.QNAME, true, getParamDisplayLabel(PARAM_ASPECT_NAME), false, "ac-aspects"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -542,7 +542,10 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
@Override
|
||||
protected void onShutdown(ApplicationEvent applicationEvent)
|
||||
{
|
||||
// NOOP
|
||||
if (eventSender != null)
|
||||
{
|
||||
eventSender.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
protected class EventTransactionListener extends TransactionListenerAdapter
|
||||
@@ -816,4 +819,4 @@ public class EventGenerator extends AbstractLifecycleBean implements Initializin
|
||||
return peerAssocs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ public interface EventSender
|
||||
}
|
||||
|
||||
/**
|
||||
* It's called when the bean instance is destroyed, allowing to perform cleanup operations.
|
||||
* It's called when the application context is closing, allowing {@link org.alfresco.repo.event2.EventGenerator} to perform cleanup operations.
|
||||
*/
|
||||
default void destroy()
|
||||
{
|
||||
@@ -63,4 +63,4 @@ public interface EventSender
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,16 +25,15 @@
|
||||
*/
|
||||
package org.alfresco.repo.event2;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
import jakarta.annotation.Nonnull;
|
||||
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
import org.springframework.core.env.PropertyResolver;
|
||||
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
|
||||
{
|
||||
@@ -52,7 +51,7 @@ public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
|
||||
private boolean legacySkipQueueConfig;
|
||||
|
||||
public EventSenderFactoryBean(@Autowired PropertyResolver propertyResolver, Event2MessageProducer event2MessageProducer,
|
||||
Executor enqueueThreadPoolExecutor, Executor dequeueThreadPoolExecutor)
|
||||
Executor enqueueThreadPoolExecutor, Executor dequeueThreadPoolExecutor)
|
||||
{
|
||||
super();
|
||||
PropertyCheck.mandatory(this, "propertyResolver", propertyResolver);
|
||||
@@ -156,13 +155,4 @@ public class EventSenderFactoryBean extends AbstractFactoryBean<EventSender>
|
||||
{
|
||||
return event2MessageProducer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroyInstance(EventSender eventSender)
|
||||
{
|
||||
if (eventSender != null)
|
||||
{
|
||||
eventSender.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.transform.config.CoreFunction;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
@@ -47,7 +46,6 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import static org.alfresco.model.ContentModel.PROP_CONTENT;
|
||||
import static org.alfresco.transform.common.RequestParamMap.DIRECT_ACCESS_URL;
|
||||
@@ -70,7 +68,6 @@ public class LocalTransformClient implements TransformClient, InitializingBean
|
||||
private ContentService contentService;
|
||||
private RenditionService2Impl renditionService2;
|
||||
private boolean directAccessUrlEnabled;
|
||||
private int threadPoolSize;
|
||||
|
||||
private ExecutorService executorService;
|
||||
private ThreadLocal<LocalTransform> transform = new ThreadLocal<>();
|
||||
@@ -100,11 +97,6 @@ public class LocalTransformClient implements TransformClient, InitializingBean
|
||||
this.directAccessUrlEnabled = directAccessUrlEnabled;
|
||||
}
|
||||
|
||||
public void setThreadPoolSize(int threadPoolSize)
|
||||
{
|
||||
this.threadPoolSize = threadPoolSize;
|
||||
}
|
||||
|
||||
public void setExecutorService(ExecutorService executorService)
|
||||
{
|
||||
this.executorService = executorService;
|
||||
@@ -118,11 +110,9 @@ public class LocalTransformClient implements TransformClient, InitializingBean
|
||||
PropertyCheck.mandatory(this, "contentService", contentService);
|
||||
PropertyCheck.mandatory(this, "renditionService2", renditionService2);
|
||||
PropertyCheck.mandatory(this, "directAccessUrlEnabled", directAccessUrlEnabled);
|
||||
PropertyCheck.mandatory(this, "threadPoolSize", threadPoolSize);
|
||||
if (executorService == null)
|
||||
{
|
||||
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("local-transform-%d").build();
|
||||
executorService = Executors.newFixedThreadPool(threadPoolSize, threadFactory);
|
||||
executorService = Executors.newCachedThreadPool();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* Copyright (C) 2005 - 2022 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
@@ -25,24 +25,6 @@
|
||||
*/
|
||||
package org.alfresco.repo.rendition2;
|
||||
|
||||
import static org.alfresco.model.ContentModel.PROP_CONTENT;
|
||||
import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE;
|
||||
import static org.alfresco.service.namespace.QName.createQName;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.model.RenditionModel;
|
||||
import org.alfresco.repo.content.ContentServicePolicies;
|
||||
@@ -69,6 +51,23 @@ import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.namespace.RegexQNamePattern;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.PropertyCheck;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.alfresco.model.ContentModel.PROP_CONTENT;
|
||||
import static org.alfresco.model.RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE;
|
||||
import static org.alfresco.service.namespace.QName.createQName;
|
||||
|
||||
/**
|
||||
* The Async Rendition service. Replaces the original deprecated RenditionService.
|
||||
@@ -81,19 +80,11 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
|
||||
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 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 int SOURCE_HAS_NO_CONTENT = -1;
|
||||
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);
|
||||
|
||||
// As Async transforms and renditions are so similar, this class provides a way to provide the code that is different.
|
||||
@@ -104,10 +95,12 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
abstract RenditionDefinition2 getRenditionDefinition();
|
||||
|
||||
void handleUnsupported(UnsupportedOperationException e)
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
void throwIllegalStateExceptionIfAlreadyDone(int sourceContentHashCode)
|
||||
{}
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private TransactionService transactionService;
|
||||
@@ -224,7 +217,8 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
@Override
|
||||
public void transform(NodeRef sourceNodeRef, TransformDefinition transformDefinition)
|
||||
{
|
||||
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() {
|
||||
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
@@ -243,7 +237,8 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
@Override
|
||||
public void render(NodeRef sourceNodeRef, String renditionName)
|
||||
{
|
||||
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack() {
|
||||
requestAsyncTransformOrRendition(sourceNodeRef, new RenderOrTransformCallBack()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
@@ -266,7 +261,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
@Override
|
||||
public void handleUnsupported(UnsupportedOperationException e)
|
||||
{
|
||||
// On the initial request for a rendition throw the exception.
|
||||
// On the initial request for a rendition throw the exception.
|
||||
NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
|
||||
if (renditionNode == null)
|
||||
{
|
||||
@@ -282,7 +277,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode + " hashCodes");
|
||||
logger.debug(getName() + ": Source " + sourceContentHashCode + " rendition " + renditionContentHashCode+ " hashCodes");
|
||||
}
|
||||
if (renditionContentHashCode == sourceContentHashCode)
|
||||
{
|
||||
@@ -296,7 +291,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!isAsyncAllowed(renderOrTransform))
|
||||
if (!isEnabled())
|
||||
{
|
||||
throw new RenditionService2Exception("Async transforms and renditions are disabled " +
|
||||
"(system.thumbnail.generate=false or renditionService2.enabled=false).");
|
||||
@@ -304,14 +299,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
|
||||
if (!nodeService.exists(sourceNodeRef))
|
||||
{
|
||||
throw new IllegalArgumentException(renderOrTransform.getName() + ": The supplied sourceNodeRef " + sourceNodeRef + " does not exist.");
|
||||
throw new IllegalArgumentException(renderOrTransform.getName()+ ": The supplied sourceNodeRef "+sourceNodeRef+" does not exist.");
|
||||
}
|
||||
|
||||
RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition();
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(renderOrTransform.getName() + ": transform " + sourceNodeRef);
|
||||
logger.debug(renderOrTransform.getName()+ ": transform " +sourceNodeRef);
|
||||
}
|
||||
|
||||
AtomicBoolean supported = new AtomicBoolean(true);
|
||||
@@ -333,13 +328,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
String user = AuthenticationUtil.getRunAsUser();
|
||||
RetryingTransactionHelper.RetryingTransactionCallback callback = () -> {
|
||||
RetryingTransactionHelper.RetryingTransactionCallback callback = () ->
|
||||
{
|
||||
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
|
||||
if (!supported.get())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(renderOrTransform.getName() + " is not supported. " +
|
||||
logger.debug(renderOrTransform.getName() +" is not supported. " +
|
||||
"The content might be too big or the source mimetype cannot be converted.");
|
||||
}
|
||||
failure(sourceNodeRef, renditionDefinition, sourceContentHashCode);
|
||||
@@ -376,24 +372,26 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
public void failure(NodeRef sourceNodeRef, RenditionDefinition2 renditionDefinition, int transformContentHashCode)
|
||||
{
|
||||
// The original transaction may have already have failed
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
|
||||
consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode);
|
||||
return null;
|
||||
}, false, true));
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () ->
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(() ->
|
||||
{
|
||||
consume(sourceNodeRef, null, renditionDefinition, transformContentHashCode);
|
||||
return null;
|
||||
}, false, true));
|
||||
}
|
||||
|
||||
public void consume(NodeRef sourceNodeRef, InputStream transformInputStream, RenditionDefinition2 renditionDefinition,
|
||||
int transformContentHashCode)
|
||||
int transformContentHashCode)
|
||||
{
|
||||
int sourceContentHashCode = getSourceContentHashCode(sourceNodeRef);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Consume: Source " + sourceContentHashCode + " and transform's source " + transformContentHashCode + " hashcodes");
|
||||
logger.debug("Consume: Source " + sourceContentHashCode + " and transform's source " + transformContentHashCode+" hashcodes");
|
||||
}
|
||||
|
||||
if (renditionDefinition instanceof TransformDefinition)
|
||||
{
|
||||
TransformDefinition transformDefinition = (TransformDefinition) renditionDefinition;
|
||||
TransformDefinition transformDefinition = (TransformDefinition)renditionDefinition;
|
||||
String targetMimetype = transformDefinition.getTargetMimetype();
|
||||
if (AsynchronousExtractor.isMetadataExtractMimetype(targetMimetype))
|
||||
{
|
||||
@@ -415,7 +413,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
private void consumeExtractedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream,
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
{
|
||||
if (transformInputStream == null)
|
||||
{
|
||||
@@ -442,7 +440,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
private void consumeEmbeddedMetadata(NodeRef nodeRef, int sourceContentHashCode, InputStream transformInputStream,
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
{
|
||||
if (transformInputStream == null)
|
||||
{
|
||||
@@ -470,7 +468,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
private void consumeTransformReply(NodeRef sourceNodeRef, InputStream transformInputStream,
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
TransformDefinition transformDefinition, int transformContentHashCode)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
@@ -486,10 +484,12 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a transformation (InputStream) and attaches it as a rendition to the source node. Does nothing if there is already a newer rendition. If the transformInputStream is null, this is taken to be a transform failure.
|
||||
* Takes a transformation (InputStream) and attaches it as a rendition to the source node.
|
||||
* Does nothing if there is already a newer rendition.
|
||||
* If the transformInputStream is null, this is taken to be a transform failure.
|
||||
*/
|
||||
private void consumeRendition(NodeRef sourceNodeRef, int sourceContentHashCode, InputStream transformInputStream,
|
||||
RenditionDefinition2 renditionDefinition, int transformContentHashCode)
|
||||
RenditionDefinition2 renditionDefinition, int transformContentHashCode)
|
||||
{
|
||||
String renditionName = renditionDefinition.getRenditionName();
|
||||
if (transformContentHashCode != sourceContentHashCode)
|
||||
@@ -507,92 +507,93 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
(transformInputStream == null ? " to null as the transform failed" : " to the transform result"));
|
||||
}
|
||||
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
|
||||
// Ensure that the creation of a rendition does not cause updates to the modified, modifier properties on the source node
|
||||
NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
|
||||
boolean createRenditionNode = renditionNode == null;
|
||||
boolean sourceHasAspectRenditioned = nodeService.hasAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED);
|
||||
try
|
||||
{
|
||||
ruleService.disableRuleType(RuleType.UPDATE);
|
||||
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
|
||||
// If they do not exist create the rendition association and the rendition node.
|
||||
if (createRenditionNode)
|
||||
{
|
||||
renditionNode = createRenditionNode(sourceNodeRef, renditionDefinition);
|
||||
}
|
||||
else if (!nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
{
|
||||
nodeService.addAspect(renditionNode, RenditionModel.ASPECT_RENDITION2, null);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Added rendition2 aspect to rendition " + renditionName + " on " + sourceNodeRef);
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Set ThumbnailLastModified for " + renditionName);
|
||||
}
|
||||
setThumbnailLastModified(sourceNodeRef, renditionName);
|
||||
|
||||
if (transformInputStream != null)
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () ->
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(() ->
|
||||
{
|
||||
// Ensure that the creation of a rendition does not cause updates to the modified, modifier properties on the source node
|
||||
NodeRef renditionNode = getRenditionNode(sourceNodeRef, renditionName);
|
||||
boolean createRenditionNode = renditionNode == null;
|
||||
boolean sourceHasAspectRenditioned = nodeService.hasAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED);
|
||||
try
|
||||
{
|
||||
// Set or replace rendition content
|
||||
ContentWriter contentWriter = contentService.getWriter(renditionNode, DEFAULT_RENDITION_CONTENT_PROP, true);
|
||||
String targetMimetype = renditionDefinition.getTargetMimetype();
|
||||
contentWriter.setMimetype(targetMimetype);
|
||||
contentWriter.setEncoding(DEFAULT_ENCODING);
|
||||
ContentWriter renditionWriter = contentWriter;
|
||||
renditionWriter.putContent(transformInputStream);
|
||||
ruleService.disableRuleType(RuleType.UPDATE);
|
||||
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||
behaviourFilter.disableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
|
||||
ContentReader contentReader = renditionWriter.getReader();
|
||||
long sizeOfRendition = contentReader.getSize();
|
||||
if (sizeOfRendition > 0L)
|
||||
// If they do not exist create the rendition association and the rendition node.
|
||||
if (createRenditionNode)
|
||||
{
|
||||
renditionNode = createRenditionNode(sourceNodeRef, renditionDefinition);
|
||||
}
|
||||
else if (!nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
{
|
||||
nodeService.addAspect(renditionNode, RenditionModel.ASPECT_RENDITION2, null);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Set rendition hashcode for " + renditionName);
|
||||
logger.debug("Added rendition2 aspect to rendition " + renditionName + " on " + sourceNodeRef);
|
||||
}
|
||||
}
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Set ThumbnailLastModified for " + renditionName);
|
||||
}
|
||||
setThumbnailLastModified(sourceNodeRef, renditionName);
|
||||
|
||||
if (transformInputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Set or replace rendition content
|
||||
ContentWriter contentWriter = contentService.getWriter(renditionNode, DEFAULT_RENDITION_CONTENT_PROP, true);
|
||||
String targetMimetype = renditionDefinition.getTargetMimetype();
|
||||
contentWriter.setMimetype(targetMimetype);
|
||||
contentWriter.setEncoding(DEFAULT_ENCODING);
|
||||
ContentWriter renditionWriter = contentWriter;
|
||||
renditionWriter.putContent(transformInputStream);
|
||||
|
||||
ContentReader contentReader = renditionWriter.getReader();
|
||||
long sizeOfRendition = contentReader.getSize();
|
||||
if (sizeOfRendition > 0L)
|
||||
{
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Set rendition hashcode for " + renditionName);
|
||||
}
|
||||
nodeService.setProperty(renditionNode, RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE, transformContentHashCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error("Transform was zero bytes for " + renditionName + " on " + sourceNodeRef);
|
||||
clearRenditionContentData(renditionNode);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.error("Failed to copy transform InputStream into rendition " + renditionName + " on " + sourceNodeRef);
|
||||
throw e;
|
||||
}
|
||||
nodeService.setProperty(renditionNode, RenditionModel.PROP_RENDITION_CONTENT_HASH_CODE, transformContentHashCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.error("Transform was zero bytes for " + renditionName + " on " + sourceNodeRef);
|
||||
clearRenditionContentData(renditionNode);
|
||||
}
|
||||
|
||||
if (!sourceHasAspectRenditioned)
|
||||
{
|
||||
nodeService.addAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED, null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.error("Failed to copy transform InputStream into rendition " + renditionName + " on " + sourceNodeRef);
|
||||
throw e;
|
||||
throw new RenditionService2Exception(TRANSFORMING_ERROR_MESSAGE + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clearRenditionContentData(renditionNode);
|
||||
}
|
||||
|
||||
if (!sourceHasAspectRenditioned)
|
||||
{
|
||||
nodeService.addAspect(sourceNodeRef, RenditionModel.ASPECT_RENDITIONED, null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RenditionService2Exception(TRANSFORMING_ERROR_MESSAGE + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
ruleService.enableRuleType(RuleType.UPDATE);
|
||||
}
|
||||
return null;
|
||||
}, false, true));
|
||||
finally
|
||||
{
|
||||
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_AUDITABLE);
|
||||
behaviourFilter.enableBehaviour(sourceNodeRef, ContentModel.ASPECT_VERSIONABLE);
|
||||
ruleService.enableRuleType(RuleType.UPDATE);
|
||||
}
|
||||
return null;
|
||||
}, false, true));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,14 +634,14 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("Setting thumbnail last modified date to " + lastModifiedValue + " on source node: " + sourceNodeRef);
|
||||
logger.trace("Setting thumbnail last modified date to " + lastModifiedValue +" on source node: " + sourceNodeRef);
|
||||
}
|
||||
|
||||
if (nodeService.hasAspect(sourceNodeRef, ContentModel.ASPECT_THUMBNAIL_MODIFICATION))
|
||||
{
|
||||
List<String> thumbnailMods = (List<String>) nodeService.getProperty(sourceNodeRef, ContentModel.PROP_LAST_THUMBNAIL_MODIFICATION_DATA);
|
||||
String target = null;
|
||||
for (String currThumbnailMod : thumbnailMods)
|
||||
for (String currThumbnailMod: thumbnailMods)
|
||||
{
|
||||
if (currThumbnailMod.startsWith(prefix))
|
||||
{
|
||||
@@ -664,7 +665,8 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code of the source node's content url. As transformations may be returned in a different sequences to which they were requested, this is used work out if a rendition should be replaced.
|
||||
* Returns the hash code of the source node's content url. As transformations may be returned in a different
|
||||
* sequences to which they were requested, this is used work out if a rendition should be replaced.
|
||||
*/
|
||||
private int getSourceContentHashCode(NodeRef sourceNodeRef)
|
||||
{
|
||||
@@ -673,7 +675,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
if (contentData != null)
|
||||
{
|
||||
// Originally we used the contentData URL, but that is not enough if the mimetype changes.
|
||||
String contentString = contentData.getContentUrl() + contentData.getMimetype();
|
||||
String contentString = contentData.getContentUrl()+contentData.getMimetype();
|
||||
if (contentString != null)
|
||||
{
|
||||
hashCode = contentString.hashCode();
|
||||
@@ -683,11 +685,13 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code of source node's content url on the rendition node (node may be null) if it does not exist. Used work out if a rendition should be replaced. {@code -2} is returned if the rendition does not exist or was not created by RenditionService2. {@code -1} is returned if there was no source content or the rendition failed.
|
||||
* Returns the hash code of source node's content url on the rendition node (node may be null) if it does not exist.
|
||||
* Used work out if a rendition should be replaced. {@code -2} is returned if the rendition does not exist or was
|
||||
* not created by RenditionService2. {@code -1} is returned if there was no source content or the rendition failed.
|
||||
*/
|
||||
private int getRenditionContentHashCode(NodeRef renditionNode)
|
||||
{
|
||||
if (renditionNode == null || !nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
if ( renditionNode == null || !nodeService.hasAspect(renditionNode, RenditionModel.ASPECT_RENDITION2))
|
||||
{
|
||||
return RENDITION2_DOES_NOT_EXIST;
|
||||
}
|
||||
@@ -695,7 +699,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
Serializable hashCode = nodeService.getProperty(renditionNode, PROP_RENDITION_CONTENT_HASH_CODE);
|
||||
return hashCode == null
|
||||
? SOURCE_HAS_NO_CONTENT
|
||||
: (int) hashCode;
|
||||
: (int)hashCode;
|
||||
}
|
||||
|
||||
private NodeRef getRenditionNode(NodeRef sourceNodeRef, String renditionName)
|
||||
@@ -769,12 +773,11 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks whether the specified source node is of a content class which has been registered for rendition prevention.
|
||||
* This method checks whether the specified source node is of a content class which has been registered for
|
||||
* rendition prevention.
|
||||
*
|
||||
* @param sourceNode
|
||||
* the node to check.
|
||||
* @throws RenditionService2PreventedException
|
||||
* if the source node is configured for rendition prevention.
|
||||
* @param sourceNode the node to check.
|
||||
* @throws RenditionService2PreventedException if the source node is configured for rendition prevention.
|
||||
*/
|
||||
// This code is based on the old RenditionServiceImpl.checkSourceNodeForPreventionClass(...)
|
||||
private void checkSourceNodeForPreventionClass(NodeRef sourceNode)
|
||||
@@ -820,7 +823,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
|
||||
for (ChildAssociationRef childAssoc : childAsocs)
|
||||
{
|
||||
NodeRef renditionNode = childAssoc.getChildRef();
|
||||
NodeRef renditionNode = childAssoc.getChildRef();
|
||||
if (isRenditionAvailable(sourceNodeRef, renditionNode))
|
||||
{
|
||||
result.add(childAssoc);
|
||||
@@ -830,7 +833,8 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the rendition is available. Failed renditions (there was an error) don't have a contentUrl and out of date renditions or those still being created don't have a matching contentHashCode.
|
||||
* Indicates if the rendition is available. Failed renditions (there was an error) don't have a contentUrl
|
||||
* and out of date renditions or those still being created don't have a matching contentHashCode.
|
||||
*/
|
||||
public boolean isRenditionAvailable(NodeRef sourceNodeRef, NodeRef renditionNode)
|
||||
{
|
||||
@@ -848,7 +852,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
int renditionContentHashCode = getRenditionContentHashCode(renditionNode);
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode + " hashcodes");
|
||||
logger.debug("isRenditionAvailable source " + sourceContentHashCode + " and rendition " + renditionContentHashCode+" hashcodes");
|
||||
}
|
||||
if (sourceContentHashCode != renditionContentHashCode)
|
||||
{
|
||||
@@ -888,17 +892,19 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
|
||||
}
|
||||
ChildAssociationRef childAssoc = renditions.get(0);
|
||||
NodeRef renditionNode = childAssoc.getChildRef();
|
||||
return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null : childAssoc;
|
||||
return !isRenditionAvailable(sourceNodeRef, renditionNode) ? null: childAssoc;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearRenditionContentDataInTransaction(NodeRef renditionNode)
|
||||
{
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () -> transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
|
||||
clearRenditionContentData(renditionNode);
|
||||
return null;
|
||||
}, false, true));
|
||||
AuthenticationUtil.runAsSystem((AuthenticationUtil.RunAsWork<Void>) () ->
|
||||
transactionService.getRetryingTransactionHelper().doInTransaction(() ->
|
||||
{
|
||||
clearRenditionContentData(renditionNode);
|
||||
return null;
|
||||
}, false, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -944,23 +950,4 @@ 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,121 +1,123 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.OIDCUserInfo;
|
||||
|
||||
/**
|
||||
*
|
||||
* Authenticates a user against Identity Service (Keycloak/Authorization Server). {@link IdentityServiceFacade} is used to verify provided user credentials. User is set as the current user if the user credentials are valid. <br>
|
||||
* The {@link IdentityServiceAuthenticationComponent#identityServiceFacade} can be null in which case this authenticator will just fall through to the next one in the chain.
|
||||
*
|
||||
*/
|
||||
public class IdentityServiceAuthenticationComponent extends AbstractAuthenticationComponent implements ActivateableBean
|
||||
{
|
||||
private final Log LOGGER = LogFactory.getLog(IdentityServiceAuthenticationComponent.class);
|
||||
/** client used to authenticate user credentials against Authorization Server **/
|
||||
private IdentityServiceFacade identityServiceFacade;
|
||||
/** enabled flag for the identity service subsystem **/
|
||||
private boolean active;
|
||||
|
||||
private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
|
||||
|
||||
private boolean allowGuestLogin;
|
||||
|
||||
public void setIdentityServiceFacade(IdentityServiceFacade identityServiceFacade)
|
||||
{
|
||||
this.identityServiceFacade = identityServiceFacade;
|
||||
}
|
||||
|
||||
public void setAllowGuestLogin(boolean allowGuestLogin)
|
||||
{
|
||||
this.allowGuestLogin = allowGuestLogin;
|
||||
}
|
||||
|
||||
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
|
||||
{
|
||||
this.jitProvisioningHandler = jitProvisioningHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticateImpl(String userName, char[] password) throws AuthenticationException
|
||||
{
|
||||
if (identityServiceFacade == null)
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("IdentityServiceFacade was not set, possibly due to the 'identity-service.authentication.enable-username-password-authentication=false' property.");
|
||||
}
|
||||
|
||||
throw new AuthenticationException("User not authenticated because IdentityServiceFacade was not set.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt to verify user credentials
|
||||
IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(AuthorizationGrant.password(userName, String.valueOf(password)));
|
||||
|
||||
String normalizedUsername = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(accessTokenAuthorization.getAccessToken().getTokenValue())
|
||||
.map(OIDCUserInfo::username)
|
||||
.orElseThrow(() -> new AuthenticationException("Failed to extract username from token and user info endpoint."));
|
||||
// Verification was successful so treat as authenticated user
|
||||
setCurrentUser(normalizedUsername);
|
||||
}
|
||||
catch (IdentityServiceFacadeException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to verify user credentials against the OAuth2 Authorization Server.", e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to verify user credentials.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setActive(boolean active)
|
||||
{
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean implementationAllowsGuestLogin()
|
||||
{
|
||||
return allowGuestLogin;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Authenticates a user against Identity Service (Keycloak/Authorization Server).
|
||||
* {@link IdentityServiceFacade} is used to verify provided user credentials. User is set as the current user if the
|
||||
* user credentials are valid.
|
||||
* <br>
|
||||
* The {@link IdentityServiceAuthenticationComponent#identityServiceFacade} can be null in which case this authenticator
|
||||
* will just fall through to the next one in the chain.
|
||||
*
|
||||
*/
|
||||
public class IdentityServiceAuthenticationComponent extends AbstractAuthenticationComponent implements ActivateableBean
|
||||
{
|
||||
private final Log LOGGER = LogFactory.getLog(IdentityServiceAuthenticationComponent.class);
|
||||
/** client used to authenticate user credentials against Authorization Server **/
|
||||
private IdentityServiceFacade identityServiceFacade;
|
||||
/** enabled flag for the identity service subsystem**/
|
||||
private boolean active;
|
||||
|
||||
private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
|
||||
|
||||
private boolean allowGuestLogin;
|
||||
|
||||
public void setIdentityServiceFacade(IdentityServiceFacade identityServiceFacade)
|
||||
{
|
||||
this.identityServiceFacade = identityServiceFacade;
|
||||
}
|
||||
|
||||
public void setAllowGuestLogin(boolean allowGuestLogin)
|
||||
{
|
||||
this.allowGuestLogin = allowGuestLogin;
|
||||
}
|
||||
|
||||
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
|
||||
{
|
||||
this.jitProvisioningHandler = jitProvisioningHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticateImpl(String userName, char[] password) throws AuthenticationException
|
||||
{
|
||||
if (identityServiceFacade == null)
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("IdentityServiceFacade was not set, possibly due to the 'identity-service.authentication.enable-username-password-authentication=false' property.");
|
||||
}
|
||||
|
||||
throw new AuthenticationException("User not authenticated because IdentityServiceFacade was not set.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt to verify user credentials
|
||||
IdentityServiceFacade.AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(AuthorizationGrant.password(userName, String.valueOf(password)));
|
||||
|
||||
String normalizedUsername = jitProvisioningHandler.extractUserInfoAndCreateUserIfNeeded(accessTokenAuthorization.getAccessToken().getTokenValue())
|
||||
.map(OIDCUserInfo::username)
|
||||
.orElseThrow(() -> new AuthenticationException("Failed to extract username from token and user info endpoint."));
|
||||
// Verification was successful so treat as authenticated user
|
||||
setCurrentUser(normalizedUsername);
|
||||
}
|
||||
catch (IdentityServiceFacadeException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to verify user credentials against the OAuth2 Authorization Server.", e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
throw new AuthenticationException("Failed to verify user credentials.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void setActive(boolean active)
|
||||
{
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive()
|
||||
{
|
||||
return active;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean implementationAllowsGuestLogin()
|
||||
{
|
||||
return allowGuestLogin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,413 +1,330 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Class to hold configuration for the Identity Service.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
@SuppressWarnings("PMD.ExcessivePublicCount")
|
||||
public class IdentityServiceConfig
|
||||
{
|
||||
private static final String REALMS = "realms";
|
||||
|
||||
private int clientConnectionTimeout;
|
||||
private int clientSocketTimeout;
|
||||
private String issuerUrl;
|
||||
private String audience;
|
||||
// client id
|
||||
private String resource;
|
||||
private String clientSecret;
|
||||
private String authServerUrl;
|
||||
private String realm;
|
||||
private int connectionPoolSize;
|
||||
private boolean allowAnyHostname;
|
||||
private boolean disableTrustManager;
|
||||
private String truststore;
|
||||
private String truststorePassword;
|
||||
private String clientKeystore;
|
||||
private String clientKeystorePassword;
|
||||
private String clientKeyPassword;
|
||||
private String realmKey;
|
||||
private int publicKeyCacheTtl;
|
||||
private boolean publicClient;
|
||||
private String principalAttribute;
|
||||
private boolean clientIdValidationDisabled;
|
||||
private String adminConsoleRedirectPath;
|
||||
private String signatureAlgorithms;
|
||||
private String adminConsoleScopes;
|
||||
private String passwordGrantScopes;
|
||||
private String issuerAttribute;
|
||||
private String firstNameAttribute;
|
||||
private String lastNameAttribute;
|
||||
private String emailAttribute;
|
||||
private long jwtClockSkewMs;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Client connection timeout in milliseconds.
|
||||
*/
|
||||
public int getClientConnectionTimeout()
|
||||
{
|
||||
return clientConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientConnectionTimeout
|
||||
* Client connection timeout in milliseconds.
|
||||
*/
|
||||
public void setClientConnectionTimeout(int clientConnectionTimeout)
|
||||
{
|
||||
this.clientConnectionTimeout = clientConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Client socket timeout in milliseconds.s
|
||||
*/
|
||||
public int getClientSocketTimeout()
|
||||
{
|
||||
return clientSocketTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientSocketTimeout
|
||||
* Client socket timeout in milliseconds.
|
||||
*/
|
||||
public void setClientSocketTimeout(int clientSocketTimeout)
|
||||
{
|
||||
this.clientSocketTimeout = clientSocketTimeout;
|
||||
}
|
||||
|
||||
public void setConnectionPoolSize(int connectionPoolSize)
|
||||
{
|
||||
this.connectionPoolSize = connectionPoolSize;
|
||||
}
|
||||
|
||||
public int getConnectionPoolSize()
|
||||
{
|
||||
return connectionPoolSize;
|
||||
}
|
||||
|
||||
public String getIssuerUrl()
|
||||
{
|
||||
return issuerUrl;
|
||||
}
|
||||
|
||||
public void setIssuerUrl(String issuerUrl)
|
||||
{
|
||||
this.issuerUrl = issuerUrl;
|
||||
}
|
||||
|
||||
public String getAudience()
|
||||
{
|
||||
return audience;
|
||||
}
|
||||
|
||||
public void setAudience(String audience)
|
||||
{
|
||||
this.audience = audience;
|
||||
}
|
||||
|
||||
public String getAuthServerUrl()
|
||||
{
|
||||
return Optional.ofNullable(realm)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.filter(realm -> StringUtils.isNotBlank(authServerUrl))
|
||||
.map(realm -> UriComponentsBuilder.fromUriString(authServerUrl)
|
||||
.pathSegment(REALMS, realm)
|
||||
.build()
|
||||
.toString())
|
||||
.orElse(authServerUrl);
|
||||
}
|
||||
|
||||
public void setAuthServerUrl(String authServerUrl)
|
||||
{
|
||||
this.authServerUrl = authServerUrl;
|
||||
}
|
||||
|
||||
public String getRealm()
|
||||
{
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(String realm)
|
||||
{
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public String getResource()
|
||||
{
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource)
|
||||
{
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret)
|
||||
{
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public String getClientSecret()
|
||||
{
|
||||
return Optional.ofNullable(clientSecret)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public void setAllowAnyHostname(boolean allowAnyHostname)
|
||||
{
|
||||
this.allowAnyHostname = allowAnyHostname;
|
||||
}
|
||||
|
||||
public boolean isAllowAnyHostname()
|
||||
{
|
||||
return allowAnyHostname;
|
||||
}
|
||||
|
||||
public void setDisableTrustManager(boolean disableTrustManager)
|
||||
{
|
||||
this.disableTrustManager = disableTrustManager;
|
||||
}
|
||||
|
||||
public boolean isDisableTrustManager()
|
||||
{
|
||||
return disableTrustManager;
|
||||
}
|
||||
|
||||
public void setTruststore(String truststore)
|
||||
{
|
||||
this.truststore = truststore;
|
||||
}
|
||||
|
||||
public String getTruststore()
|
||||
{
|
||||
return truststore;
|
||||
}
|
||||
|
||||
public void setTruststorePassword(String truststorePassword)
|
||||
{
|
||||
this.truststorePassword = truststorePassword;
|
||||
}
|
||||
|
||||
public String getTruststorePassword()
|
||||
{
|
||||
return truststorePassword;
|
||||
}
|
||||
|
||||
public void setClientKeystore(String clientKeystore)
|
||||
{
|
||||
this.clientKeystore = clientKeystore;
|
||||
}
|
||||
|
||||
public String getClientKeystore()
|
||||
{
|
||||
return clientKeystore;
|
||||
}
|
||||
|
||||
public void setClientKeystorePassword(String clientKeystorePassword)
|
||||
{
|
||||
this.clientKeystorePassword = clientKeystorePassword;
|
||||
}
|
||||
|
||||
public String getClientKeystorePassword()
|
||||
{
|
||||
return clientKeystorePassword;
|
||||
}
|
||||
|
||||
public void setClientKeyPassword(String clientKeyPassword)
|
||||
{
|
||||
this.clientKeyPassword = clientKeyPassword;
|
||||
}
|
||||
|
||||
public String getClientKeyPassword()
|
||||
{
|
||||
return clientKeyPassword;
|
||||
}
|
||||
|
||||
public void setRealmKey(String realmKey)
|
||||
{
|
||||
this.realmKey = realmKey;
|
||||
}
|
||||
|
||||
public String getRealmKey()
|
||||
{
|
||||
return realmKey;
|
||||
}
|
||||
|
||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl)
|
||||
{
|
||||
this.publicKeyCacheTtl = publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public int getPublicKeyCacheTtl()
|
||||
{
|
||||
return publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public void setPublicClient(boolean publicClient)
|
||||
{
|
||||
this.publicClient = publicClient;
|
||||
}
|
||||
|
||||
public boolean isPublicClient()
|
||||
{
|
||||
return publicClient;
|
||||
}
|
||||
|
||||
public String getPrincipalAttribute()
|
||||
{
|
||||
return principalAttribute;
|
||||
}
|
||||
|
||||
public void setPrincipalAttribute(String principalAttribute)
|
||||
{
|
||||
this.principalAttribute = principalAttribute;
|
||||
}
|
||||
|
||||
public boolean isClientIdValidationDisabled()
|
||||
{
|
||||
return clientIdValidationDisabled;
|
||||
}
|
||||
|
||||
public void setClientIdValidationDisabled(boolean clientIdValidationDisabled)
|
||||
{
|
||||
this.clientIdValidationDisabled = clientIdValidationDisabled;
|
||||
}
|
||||
|
||||
public String getAdminConsoleRedirectPath()
|
||||
{
|
||||
return adminConsoleRedirectPath;
|
||||
}
|
||||
|
||||
public void setAdminConsoleRedirectPath(String adminConsoleRedirectPath)
|
||||
{
|
||||
this.adminConsoleRedirectPath = adminConsoleRedirectPath;
|
||||
}
|
||||
|
||||
public Set<SignatureAlgorithm> getSignatureAlgorithms()
|
||||
{
|
||||
return Stream.of(signatureAlgorithms.split(","))
|
||||
.map(String::trim)
|
||||
.map(SignatureAlgorithm::from)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setSignatureAlgorithms(String signatureAlgorithms)
|
||||
{
|
||||
this.signatureAlgorithms = signatureAlgorithms;
|
||||
}
|
||||
|
||||
public String getIssuerAttribute()
|
||||
{
|
||||
return issuerAttribute;
|
||||
}
|
||||
|
||||
public void setIssuerAttribute(String issuerAttribute)
|
||||
{
|
||||
this.issuerAttribute = issuerAttribute;
|
||||
}
|
||||
|
||||
public Set<String> getAdminConsoleScopes()
|
||||
{
|
||||
return Stream.of(adminConsoleScopes.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setAdminConsoleScopes(String adminConsoleScopes)
|
||||
{
|
||||
this.adminConsoleScopes = adminConsoleScopes;
|
||||
}
|
||||
|
||||
public Set<String> getPasswordGrantScopes()
|
||||
{
|
||||
return Stream.of(passwordGrantScopes.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setPasswordGrantScopes(String passwordGrantScopes)
|
||||
{
|
||||
this.passwordGrantScopes = passwordGrantScopes;
|
||||
}
|
||||
|
||||
public void setFirstNameAttribute(String firstNameAttribute)
|
||||
{
|
||||
this.firstNameAttribute = firstNameAttribute;
|
||||
}
|
||||
|
||||
public void setLastNameAttribute(String lastNameAttribute)
|
||||
{
|
||||
this.lastNameAttribute = lastNameAttribute;
|
||||
}
|
||||
|
||||
public void setEmailAttribute(String emailAttribute)
|
||||
{
|
||||
this.emailAttribute = emailAttribute;
|
||||
}
|
||||
|
||||
public void setJwtClockSkewMs(long jwtClockSkewMs)
|
||||
{
|
||||
this.jwtClockSkewMs = jwtClockSkewMs;
|
||||
}
|
||||
|
||||
public String getFirstNameAttribute()
|
||||
{
|
||||
return firstNameAttribute;
|
||||
}
|
||||
|
||||
public String getLastNameAttribute()
|
||||
{
|
||||
return lastNameAttribute;
|
||||
}
|
||||
|
||||
public String getEmailAttribute()
|
||||
{
|
||||
return emailAttribute;
|
||||
}
|
||||
|
||||
public long getJwtClockSkewMs()
|
||||
{
|
||||
return jwtClockSkewMs;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Class to hold configuration for the Identity Service.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
@SuppressWarnings("PMD.ExcessivePublicCount")
|
||||
public class IdentityServiceConfig
|
||||
{
|
||||
private static final String REALMS = "realms";
|
||||
|
||||
private int clientConnectionTimeout;
|
||||
private int clientSocketTimeout;
|
||||
private String issuerUrl;
|
||||
private String audience;
|
||||
// client id
|
||||
private String resource;
|
||||
private String clientSecret;
|
||||
private String authServerUrl;
|
||||
private String realm;
|
||||
private int connectionPoolSize;
|
||||
private boolean allowAnyHostname;
|
||||
private boolean disableTrustManager;
|
||||
private String truststore;
|
||||
private String truststorePassword;
|
||||
private String clientKeystore;
|
||||
private String clientKeystorePassword;
|
||||
private String clientKeyPassword;
|
||||
private String realmKey;
|
||||
private int publicKeyCacheTtl;
|
||||
private boolean publicClient;
|
||||
private String principalAttribute;
|
||||
private boolean clientIdValidationDisabled;
|
||||
private String adminConsoleRedirectPath;
|
||||
private String signatureAlgorithms;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Client connection timeout in milliseconds.
|
||||
*/
|
||||
public int getClientConnectionTimeout()
|
||||
{
|
||||
return clientConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientConnectionTimeout Client connection timeout in milliseconds.
|
||||
*/
|
||||
public void setClientConnectionTimeout(int clientConnectionTimeout)
|
||||
{
|
||||
this.clientConnectionTimeout = clientConnectionTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Client socket timeout in milliseconds.s
|
||||
*/
|
||||
public int getClientSocketTimeout()
|
||||
{
|
||||
return clientSocketTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientSocketTimeout Client socket timeout in milliseconds.
|
||||
*/
|
||||
public void setClientSocketTimeout(int clientSocketTimeout)
|
||||
{
|
||||
this.clientSocketTimeout = clientSocketTimeout;
|
||||
}
|
||||
|
||||
public void setConnectionPoolSize(int connectionPoolSize)
|
||||
{
|
||||
this.connectionPoolSize = connectionPoolSize;
|
||||
}
|
||||
|
||||
public int getConnectionPoolSize()
|
||||
{
|
||||
return connectionPoolSize;
|
||||
}
|
||||
|
||||
public String getIssuerUrl()
|
||||
{
|
||||
return issuerUrl;
|
||||
}
|
||||
|
||||
public void setIssuerUrl(String issuerUrl)
|
||||
{
|
||||
this.issuerUrl = issuerUrl;
|
||||
}
|
||||
|
||||
public String getAudience()
|
||||
{
|
||||
return audience;
|
||||
}
|
||||
|
||||
public void setAudience(String audience)
|
||||
{
|
||||
this.audience = audience;
|
||||
}
|
||||
|
||||
public String getAuthServerUrl()
|
||||
{
|
||||
return Optional.ofNullable(realm)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.filter(realm -> StringUtils.isNotBlank(authServerUrl))
|
||||
.map(realm -> UriComponentsBuilder.fromUriString(authServerUrl)
|
||||
.pathSegment(REALMS, realm)
|
||||
.build()
|
||||
.toString())
|
||||
.orElse(authServerUrl);
|
||||
}
|
||||
|
||||
public void setAuthServerUrl(String authServerUrl)
|
||||
{
|
||||
this.authServerUrl = authServerUrl;
|
||||
}
|
||||
|
||||
public String getRealm()
|
||||
{
|
||||
return realm;
|
||||
}
|
||||
|
||||
public void setRealm(String realm)
|
||||
{
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public String getResource()
|
||||
{
|
||||
return resource;
|
||||
}
|
||||
|
||||
public void setResource(String resource)
|
||||
{
|
||||
this.resource = resource;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret)
|
||||
{
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public String getClientSecret()
|
||||
{
|
||||
return Optional.ofNullable(clientSecret)
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
public void setAllowAnyHostname(boolean allowAnyHostname)
|
||||
{
|
||||
this.allowAnyHostname = allowAnyHostname;
|
||||
}
|
||||
|
||||
public boolean isAllowAnyHostname()
|
||||
{
|
||||
return allowAnyHostname;
|
||||
}
|
||||
|
||||
public void setDisableTrustManager(boolean disableTrustManager)
|
||||
{
|
||||
this.disableTrustManager = disableTrustManager;
|
||||
}
|
||||
|
||||
public boolean isDisableTrustManager()
|
||||
{
|
||||
return disableTrustManager;
|
||||
}
|
||||
|
||||
public void setTruststore(String truststore)
|
||||
{
|
||||
this.truststore = truststore;
|
||||
}
|
||||
|
||||
public String getTruststore()
|
||||
{
|
||||
return truststore;
|
||||
}
|
||||
|
||||
public void setTruststorePassword(String truststorePassword)
|
||||
{
|
||||
this.truststorePassword = truststorePassword;
|
||||
}
|
||||
|
||||
public String getTruststorePassword()
|
||||
{
|
||||
return truststorePassword;
|
||||
}
|
||||
|
||||
public void setClientKeystore(String clientKeystore)
|
||||
{
|
||||
this.clientKeystore = clientKeystore;
|
||||
}
|
||||
|
||||
public String getClientKeystore()
|
||||
{
|
||||
return clientKeystore;
|
||||
}
|
||||
|
||||
public void setClientKeystorePassword(String clientKeystorePassword)
|
||||
{
|
||||
this.clientKeystorePassword = clientKeystorePassword;
|
||||
}
|
||||
|
||||
public String getClientKeystorePassword()
|
||||
{
|
||||
return clientKeystorePassword;
|
||||
}
|
||||
|
||||
public void setClientKeyPassword(String clientKeyPassword)
|
||||
{
|
||||
this.clientKeyPassword = clientKeyPassword;
|
||||
}
|
||||
|
||||
public String getClientKeyPassword()
|
||||
{
|
||||
return clientKeyPassword;
|
||||
}
|
||||
|
||||
public void setRealmKey(String realmKey)
|
||||
{
|
||||
this.realmKey = realmKey;
|
||||
}
|
||||
|
||||
public String getRealmKey()
|
||||
{
|
||||
return realmKey;
|
||||
}
|
||||
|
||||
public void setPublicKeyCacheTtl(int publicKeyCacheTtl)
|
||||
{
|
||||
this.publicKeyCacheTtl = publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public int getPublicKeyCacheTtl()
|
||||
{
|
||||
return publicKeyCacheTtl;
|
||||
}
|
||||
|
||||
public void setPublicClient(boolean publicClient)
|
||||
{
|
||||
this.publicClient = publicClient;
|
||||
}
|
||||
|
||||
public boolean isPublicClient()
|
||||
{
|
||||
return publicClient;
|
||||
}
|
||||
|
||||
public String getPrincipalAttribute()
|
||||
{
|
||||
return principalAttribute;
|
||||
}
|
||||
|
||||
public void setPrincipalAttribute(String principalAttribute)
|
||||
{
|
||||
this.principalAttribute = principalAttribute;
|
||||
}
|
||||
|
||||
public boolean isClientIdValidationDisabled()
|
||||
{
|
||||
return clientIdValidationDisabled;
|
||||
}
|
||||
|
||||
public void setClientIdValidationDisabled(boolean clientIdValidationDisabled)
|
||||
{
|
||||
this.clientIdValidationDisabled = clientIdValidationDisabled;
|
||||
}
|
||||
|
||||
public String getAdminConsoleRedirectPath()
|
||||
{
|
||||
return adminConsoleRedirectPath;
|
||||
}
|
||||
|
||||
public void setAdminConsoleRedirectPath(String adminConsoleRedirectPath)
|
||||
{
|
||||
this.adminConsoleRedirectPath = adminConsoleRedirectPath;
|
||||
}
|
||||
|
||||
public Set<SignatureAlgorithm> getSignatureAlgorithms()
|
||||
{
|
||||
return Stream.of(signatureAlgorithms.split(","))
|
||||
.map(String::trim)
|
||||
.map(SignatureAlgorithm::from)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
public void setSignatureAlgorithms(String signatureAlgorithms)
|
||||
{
|
||||
this.signatureAlgorithms = signatureAlgorithms;
|
||||
}
|
||||
}
|
||||
@@ -1,265 +1,249 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static java.util.Objects.nonNull;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.DecodedTokenUser;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
|
||||
|
||||
/**
|
||||
* Allows to interact with the Identity Service
|
||||
*/
|
||||
public interface IdentityServiceFacade
|
||||
{
|
||||
/**
|
||||
* Returns {@link AccessToken} based authorization for provided {@link AuthorizationGrant}.
|
||||
*
|
||||
* @param grant
|
||||
* the OAuth2 grant provided by the Resource Owner.
|
||||
* @return {@link AccessTokenAuthorization} containing access token and optional refresh token.
|
||||
* @throws {@link
|
||||
* AuthorizationException} when provided grant cannot be exchanged for the access token.
|
||||
*/
|
||||
AccessTokenAuthorization authorize(AuthorizationGrant grant) throws AuthorizationException;
|
||||
|
||||
/**
|
||||
* Decodes the access token into the {@link DecodedAccessToken} which contains claims connected with a given token.
|
||||
*
|
||||
* @param token
|
||||
* {@link String} with encoded access token value.
|
||||
* @return {@link DecodedAccessToken} containing decoded claims.
|
||||
* @throws {@link
|
||||
* TokenDecodingException} when token decoding failed.
|
||||
*/
|
||||
DecodedAccessToken decodeToken(String token) throws TokenDecodingException;
|
||||
|
||||
/**
|
||||
* Gets claims about the authenticated user, such as name and email address, via the UserInfo endpoint of the OpenID provider.
|
||||
*
|
||||
* @param token
|
||||
* {@link String} with encoded access token value.
|
||||
* @param userInfoAttrMapping
|
||||
* {@link UserInfoAttrMapping} containing the mapping of claims.
|
||||
* @return {@link DecodedTokenUser} containing user claims or {@link Optional#empty()} if the token does not contain a username claim.
|
||||
*/
|
||||
Optional<DecodedTokenUser> getUserInfo(String token, UserInfoAttrMapping userInfoAttrMapping);
|
||||
|
||||
/**
|
||||
* Gets a client registration
|
||||
*/
|
||||
ClientRegistration getClientRegistration();
|
||||
|
||||
class IdentityServiceFacadeException extends RuntimeException
|
||||
{
|
||||
public IdentityServiceFacadeException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
IdentityServiceFacadeException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthorizationException extends IdentityServiceFacadeException
|
||||
{
|
||||
AuthorizationException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
AuthorizationException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class UserInfoException extends IdentityServiceFacadeException
|
||||
{
|
||||
|
||||
UserInfoException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
UserInfoException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class TokenDecodingException extends IdentityServiceFacadeException
|
||||
{
|
||||
TokenDecodingException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
TokenDecodingException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents access token authorization with optional refresh token.
|
||||
*/
|
||||
interface AccessTokenAuthorization
|
||||
{
|
||||
/**
|
||||
* Required {@link AccessToken}
|
||||
*
|
||||
* @return {@link AccessToken}
|
||||
*/
|
||||
AccessToken getAccessToken();
|
||||
|
||||
/**
|
||||
* Optional refresh token.
|
||||
*
|
||||
* @return Refresh token or {@code null}
|
||||
*/
|
||||
String getRefreshTokenValue();
|
||||
}
|
||||
|
||||
interface AccessToken
|
||||
{
|
||||
String getTokenValue();
|
||||
|
||||
Instant getExpiresAt();
|
||||
}
|
||||
|
||||
interface DecodedAccessToken extends AccessToken
|
||||
{
|
||||
Object getClaim(String claim);
|
||||
}
|
||||
|
||||
class AuthorizationGrant
|
||||
{
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String refreshToken;
|
||||
private final String authorizationCode;
|
||||
private final String redirectUri;
|
||||
|
||||
private AuthorizationGrant(String username, String password, String refreshToken, String authorizationCode, String redirectUri)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.refreshToken = refreshToken;
|
||||
this.authorizationCode = authorizationCode;
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public static AuthorizationGrant password(String username, String password)
|
||||
{
|
||||
return new AuthorizationGrant(requireNonNull(username), requireNonNull(password), null, null, null);
|
||||
}
|
||||
|
||||
public static AuthorizationGrant refreshToken(String refreshToken)
|
||||
{
|
||||
return new AuthorizationGrant(null, null, requireNonNull(refreshToken), null, null);
|
||||
}
|
||||
|
||||
public static AuthorizationGrant authorizationCode(String authorizationCode, String redirectUri)
|
||||
{
|
||||
return new AuthorizationGrant(null, null, null, requireNonNull(authorizationCode), requireNonNull(redirectUri));
|
||||
}
|
||||
|
||||
boolean isPassword()
|
||||
{
|
||||
return nonNull(username);
|
||||
}
|
||||
|
||||
boolean isRefreshToken()
|
||||
{
|
||||
return nonNull(refreshToken);
|
||||
}
|
||||
|
||||
boolean isAuthorizationCode()
|
||||
{
|
||||
return nonNull(authorizationCode);
|
||||
}
|
||||
|
||||
String getUsername()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
String getPassword()
|
||||
{
|
||||
return password;
|
||||
}
|
||||
|
||||
String getRefreshToken()
|
||||
{
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
String getAuthorizationCode()
|
||||
{
|
||||
return authorizationCode;
|
||||
}
|
||||
|
||||
String getRedirectUri()
|
||||
{
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
AuthorizationGrant that = (AuthorizationGrant) o;
|
||||
return Objects.equals(username, that.username) &&
|
||||
Objects.equals(password, that.password) &&
|
||||
Objects.equals(refreshToken, that.refreshToken) &&
|
||||
Objects.equals(authorizationCode, that.authorizationCode) &&
|
||||
Objects.equals(redirectUri, that.redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(username, password, refreshToken, authorizationCode, redirectUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import static java.util.Objects.nonNull;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
|
||||
/**
|
||||
* Allows to interact with the Identity Service
|
||||
*/
|
||||
public interface IdentityServiceFacade
|
||||
{
|
||||
/**
|
||||
* Returns {@link AccessToken} based authorization for provided {@link AuthorizationGrant}.
|
||||
* @param grant the OAuth2 grant provided by the Resource Owner.
|
||||
* @return {@link AccessTokenAuthorization} containing access token and optional refresh token.
|
||||
* @throws {@link AuthorizationException} when provided grant cannot be exchanged for the access token.
|
||||
*/
|
||||
AccessTokenAuthorization authorize(AuthorizationGrant grant) throws AuthorizationException;
|
||||
|
||||
/**
|
||||
* Decodes the access token into the {@link DecodedAccessToken} which contains claims connected with a given token.
|
||||
* @param token {@link String} with encoded access token value.
|
||||
* @return {@link DecodedAccessToken} containing decoded claims.
|
||||
* @throws {@link TokenDecodingException} when token decoding failed.
|
||||
*/
|
||||
DecodedAccessToken decodeToken(String token) throws TokenDecodingException;
|
||||
|
||||
/**
|
||||
* Gets claims about the authenticated user,
|
||||
* such as name and email address, via the UserInfo endpoint of the OpenID provider.
|
||||
* @param token {@link String} with encoded access token value.
|
||||
* @param principalAttribute {@link String} the attribute name used to access the user's name from the user info response.
|
||||
* @return {@link OIDCUserInfo} containing user claims.
|
||||
*/
|
||||
Optional<OIDCUserInfo> getUserInfo(String token, String principalAttribute);
|
||||
|
||||
/**
|
||||
* Gets a client registration
|
||||
*/
|
||||
ClientRegistration getClientRegistration();
|
||||
|
||||
class IdentityServiceFacadeException extends RuntimeException
|
||||
{
|
||||
public IdentityServiceFacadeException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
IdentityServiceFacadeException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class AuthorizationException extends IdentityServiceFacadeException
|
||||
{
|
||||
AuthorizationException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
AuthorizationException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class UserInfoException extends IdentityServiceFacadeException
|
||||
{
|
||||
|
||||
UserInfoException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
UserInfoException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
class TokenDecodingException extends IdentityServiceFacadeException
|
||||
{
|
||||
TokenDecodingException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
TokenDecodingException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents access token authorization with optional refresh token.
|
||||
*/
|
||||
interface AccessTokenAuthorization
|
||||
{
|
||||
/**
|
||||
* Required {@link AccessToken}
|
||||
* @return {@link AccessToken}
|
||||
*/
|
||||
AccessToken getAccessToken();
|
||||
|
||||
/**
|
||||
* Optional refresh token.
|
||||
* @return Refresh token or {@code null}
|
||||
*/
|
||||
String getRefreshTokenValue();
|
||||
}
|
||||
|
||||
interface AccessToken {
|
||||
String getTokenValue();
|
||||
Instant getExpiresAt();
|
||||
}
|
||||
|
||||
interface DecodedAccessToken extends AccessToken
|
||||
{
|
||||
Object getClaim(String claim);
|
||||
}
|
||||
|
||||
class AuthorizationGrant {
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String refreshToken;
|
||||
private final String authorizationCode;
|
||||
private final String redirectUri;
|
||||
|
||||
private AuthorizationGrant(String username, String password, String refreshToken, String authorizationCode, String redirectUri)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.refreshToken = refreshToken;
|
||||
this.authorizationCode = authorizationCode;
|
||||
this.redirectUri = redirectUri;
|
||||
}
|
||||
|
||||
public static AuthorizationGrant password(String username, String password)
|
||||
{
|
||||
return new AuthorizationGrant(requireNonNull(username), requireNonNull(password), null, null, null);
|
||||
}
|
||||
|
||||
public static AuthorizationGrant refreshToken(String refreshToken)
|
||||
{
|
||||
return new AuthorizationGrant(null, null, requireNonNull(refreshToken), null, null);
|
||||
}
|
||||
|
||||
public static AuthorizationGrant authorizationCode(String authorizationCode, String redirectUri)
|
||||
{
|
||||
return new AuthorizationGrant(null, null, null, requireNonNull(authorizationCode), requireNonNull(redirectUri));
|
||||
}
|
||||
|
||||
boolean isPassword()
|
||||
{
|
||||
return nonNull(username);
|
||||
}
|
||||
|
||||
boolean isRefreshToken()
|
||||
{
|
||||
return nonNull(refreshToken);
|
||||
}
|
||||
|
||||
boolean isAuthorizationCode()
|
||||
{
|
||||
return nonNull(authorizationCode);
|
||||
}
|
||||
|
||||
String getUsername()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
String getPassword()
|
||||
{
|
||||
return password;
|
||||
}
|
||||
|
||||
String getRefreshToken()
|
||||
{
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
String getAuthorizationCode()
|
||||
{
|
||||
return authorizationCode;
|
||||
}
|
||||
|
||||
String getRedirectUri()
|
||||
{
|
||||
return redirectUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
if (this == o)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
AuthorizationGrant that = (AuthorizationGrant) o;
|
||||
return Objects.equals(username, that.username) &&
|
||||
Objects.equals(password, that.password) &&
|
||||
Objects.equals(refreshToken, that.refreshToken) &&
|
||||
Objects.equals(authorizationCode, that.authorizationCode) &&
|
||||
Objects.equals(redirectUri, that.redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(username, password, refreshToken, authorizationCode, redirectUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,38 +30,59 @@ import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
|
||||
import com.nimbusds.openid.connect.sdk.claims.UserInfo;
|
||||
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.AccessTokenToDecodedTokenUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.DecodedTokenUser;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.OIDCUserInfo;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.TokenUserToOIDCUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.DecodedAccessToken;
|
||||
import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* This class handles Just in Time user provisioning. It extracts {@link OIDCUserInfo} from the given bearer token and creates a new user if it does not exist in the repository.
|
||||
* This class handles Just in Time user provisioning. It extracts {@link OIDCUserInfo}
|
||||
* from {@link IdentityServiceFacade.DecodedAccessToken} or {@link UserInfo}
|
||||
* and creates a new user if it does not exist in the repository.
|
||||
*/
|
||||
public class IdentityServiceJITProvisioningHandler
|
||||
{
|
||||
private final IdentityServiceConfig identityServiceConfig;
|
||||
private final IdentityServiceFacade identityServiceFacade;
|
||||
private final PersonService personService;
|
||||
private final TransactionService transactionService;
|
||||
private final IdentityServiceConfig identityServiceConfig;
|
||||
private UserInfoAttrMapping userInfoAttrMapping;
|
||||
private TokenUserToOIDCUserMapper tokenUserToOIDCUserMapper;
|
||||
private AccessTokenToDecodedTokenUserMapper tokenToDecodedTokenUserMapper;
|
||||
|
||||
private final BiFunction<DecodedAccessToken, String, Optional<? extends OIDCUserInfo>> mapTokenToUserInfoResponse = (token, usernameMappingClaim) -> {
|
||||
Optional<String> firstName = Optional.ofNullable(token)
|
||||
.map(jwtToken -> jwtToken.getClaim(PersonClaims.GIVEN_NAME_CLAIM_NAME))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast);
|
||||
Optional<String> lastName = Optional.ofNullable(token)
|
||||
.map(jwtToken -> jwtToken.getClaim(PersonClaims.FAMILY_NAME_CLAIM_NAME))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast);
|
||||
Optional<String> email = Optional.ofNullable(token)
|
||||
.map(jwtToken -> jwtToken.getClaim(PersonClaims.EMAIL_CLAIM_NAME))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast);
|
||||
|
||||
return Optional.ofNullable(token.getClaim(Optional.ofNullable(usernameMappingClaim)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.orElse(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)))
|
||||
.filter(String.class::isInstance)
|
||||
.map(String.class::cast)
|
||||
.map(this::normalizeUserId)
|
||||
.map(username -> new OIDCUserInfo(username, firstName.orElse(""), lastName.orElse(""), email.orElse("")));
|
||||
};
|
||||
|
||||
public IdentityServiceJITProvisioningHandler(IdentityServiceFacade identityServiceFacade,
|
||||
PersonService personService,
|
||||
TransactionService transactionService,
|
||||
IdentityServiceConfig identityServiceConfig)
|
||||
PersonService personService,
|
||||
TransactionService transactionService,
|
||||
IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
this.identityServiceFacade = identityServiceFacade;
|
||||
this.personService = personService;
|
||||
@@ -69,95 +90,94 @@ public class IdentityServiceJITProvisioningHandler
|
||||
this.identityServiceConfig = identityServiceConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts {@link OIDCUserInfo} from the given bearer token and creates a new user if it does not exist in the repository. Call to the UserInfo endpoint is made only if the token does not contain a username claim or if user needs to be created and some of the {@link OIDCUserInfo} fields are empty.
|
||||
*/
|
||||
public Optional<OIDCUserInfo> extractUserInfoAndCreateUserIfNeeded(String bearerToken)
|
||||
{
|
||||
if (userInfoAttrMapping == null)
|
||||
{
|
||||
initMappers(identityServiceConfig);
|
||||
}
|
||||
Optional<OIDCUserInfo> userInfoResponse = Optional.ofNullable(bearerToken)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.flatMap(token -> extractUserInfoResponseFromAccessToken(token)
|
||||
.filter(userInfo -> StringUtils.isNotEmpty(userInfo.username()))
|
||||
.or(() -> extractUserInfoResponseFromEndpoint(token)));
|
||||
|
||||
Optional<OIDCUserInfo> oidcUserInfo = Optional.ofNullable(bearerToken)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.flatMap(token -> extractUserInfoResponseFromAccessToken(token).filter(decodedTokenUser -> StringUtils.isNotEmpty(decodedTokenUser.username()))
|
||||
.or(() -> extractUserInfoResponseFromEndpoint(token, userInfoAttrMapping)))
|
||||
.map(tokenUserToOIDCUserMapper::toOIDCUser);
|
||||
|
||||
if (transactionService.isReadOnly() || oidcUserInfo.isEmpty())
|
||||
if (transactionService.isReadOnly() || userInfoResponse.isEmpty())
|
||||
{
|
||||
return oidcUserInfo;
|
||||
return userInfoResponse;
|
||||
}
|
||||
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<>() {
|
||||
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Optional<OIDCUserInfo>>()
|
||||
{
|
||||
@Override
|
||||
public Optional<OIDCUserInfo> doWork() throws Exception
|
||||
{
|
||||
return oidcUserInfo.map(oidcUser -> {
|
||||
if (userDoesNotExistsAndCanBeCreated(oidcUser))
|
||||
return userInfoResponse.map(userInfo -> {
|
||||
if (userInfo.username() != null && personService.createMissingPeople()
|
||||
&& !personService.personExists(userInfo.username()))
|
||||
{
|
||||
|
||||
if (!oidcUser.allFieldsNotEmpty())
|
||||
if (!userInfo.allFieldsNotEmpty())
|
||||
{
|
||||
oidcUser = extractUserInfoResponseFromEndpoint(bearerToken, userInfoAttrMapping)
|
||||
.map(tokenUserToOIDCUserMapper::toOIDCUser)
|
||||
.orElse(oidcUser);
|
||||
userInfo = extractUserInfoResponseFromEndpoint(bearerToken).orElse(userInfo);
|
||||
}
|
||||
createPerson(oidcUser);
|
||||
Map<QName, Serializable> properties = new HashMap<>();
|
||||
properties.put(ContentModel.PROP_USERNAME, userInfo.username());
|
||||
properties.put(ContentModel.PROP_FIRSTNAME, userInfo.firstName());
|
||||
properties.put(ContentModel.PROP_LASTNAME, userInfo.lastName());
|
||||
properties.put(ContentModel.PROP_EMAIL, userInfo.email());
|
||||
properties.put(ContentModel.PROP_ORGID, "");
|
||||
properties.put(ContentModel.PROP_HOME_FOLDER_PROVIDER, null);
|
||||
|
||||
properties.put(ContentModel.PROP_SIZE_CURRENT, 0L);
|
||||
properties.put(ContentModel.PROP_SIZE_QUOTA, -1L); // no quota
|
||||
|
||||
personService.createPerson(properties);
|
||||
}
|
||||
return oidcUser;
|
||||
return userInfo;
|
||||
});
|
||||
}
|
||||
|
||||
}, AuthenticationUtil.getSystemUserName());
|
||||
}
|
||||
|
||||
private void initMappers(IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
this.userInfoAttrMapping = initUserInfoAttrMapping(identityServiceConfig);
|
||||
this.tokenUserToOIDCUserMapper = new TokenUserToOIDCUserMapper(personService);
|
||||
this.tokenToDecodedTokenUserMapper = new AccessTokenToDecodedTokenUserMapper(userInfoAttrMapping);
|
||||
}
|
||||
|
||||
private boolean userDoesNotExistsAndCanBeCreated(OIDCUserInfo userInfo)
|
||||
{
|
||||
return userInfo.username() != null && personService.createMissingPeople()
|
||||
&& !personService.personExists(userInfo.username());
|
||||
}
|
||||
|
||||
private Optional<DecodedTokenUser> extractUserInfoResponseFromAccessToken(String bearerToken)
|
||||
private Optional<OIDCUserInfo> extractUserInfoResponseFromAccessToken(String bearerToken)
|
||||
{
|
||||
return Optional.ofNullable(bearerToken)
|
||||
.map(identityServiceFacade::decodeToken)
|
||||
.flatMap(tokenToDecodedTokenUserMapper::toDecodedTokenUser);
|
||||
.map(identityServiceFacade::decodeToken)
|
||||
.flatMap(decodedToken -> mapTokenToUserInfoResponse.apply(decodedToken,
|
||||
identityServiceConfig.getPrincipalAttribute()));
|
||||
}
|
||||
|
||||
private Optional<DecodedTokenUser> extractUserInfoResponseFromEndpoint(String bearerToken, UserInfoAttrMapping userInfoAttrMapping)
|
||||
private Optional<OIDCUserInfo> extractUserInfoResponseFromEndpoint(String bearerToken)
|
||||
{
|
||||
return identityServiceFacade.getUserInfo(bearerToken, userInfoAttrMapping)
|
||||
.filter(userInfo -> userInfo.username() != null && !userInfo.username().isEmpty());
|
||||
return identityServiceFacade.getUserInfo(bearerToken,
|
||||
StringUtils.isNotBlank(identityServiceConfig.getPrincipalAttribute()) ?
|
||||
identityServiceConfig.getPrincipalAttribute() : PersonClaims.PREFERRED_USERNAME_CLAIM_NAME)
|
||||
.filter(userInfo -> userInfo.username() != null && !userInfo.username().isEmpty())
|
||||
.map(userInfo -> new OIDCUserInfo(normalizeUserId(userInfo.username()),
|
||||
Optional.ofNullable(userInfo.firstName()).orElse(""),
|
||||
Optional.ofNullable(userInfo.lastName()).orElse(""),
|
||||
Optional.ofNullable(userInfo.email()).orElse("")));
|
||||
}
|
||||
|
||||
private void createPerson(OIDCUserInfo userInfo)
|
||||
/**
|
||||
* Normalizes a user id, taking into account existing user accounts and case sensitivity settings.
|
||||
*
|
||||
* @param userId the user id
|
||||
* @return the string
|
||||
*/
|
||||
private String normalizeUserId(final String userId)
|
||||
{
|
||||
Map<QName, Serializable> properties = new HashMap<>();
|
||||
properties.put(ContentModel.PROP_USERNAME, userInfo.username());
|
||||
properties.put(ContentModel.PROP_FIRSTNAME, userInfo.firstName());
|
||||
properties.put(ContentModel.PROP_LASTNAME, userInfo.lastName());
|
||||
properties.put(ContentModel.PROP_EMAIL, userInfo.email());
|
||||
properties.put(ContentModel.PROP_ORGID, "");
|
||||
properties.put(ContentModel.PROP_HOME_FOLDER_PROVIDER, null);
|
||||
properties.put(ContentModel.PROP_SIZE_CURRENT, 0L);
|
||||
properties.put(ContentModel.PROP_SIZE_QUOTA, -1L); // no quota
|
||||
if (userId == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
personService.createPerson(properties);
|
||||
String normalized = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<String>()
|
||||
{
|
||||
@Override
|
||||
public String doWork() throws Exception
|
||||
{
|
||||
return personService.getUserIdentifier(userId);
|
||||
}
|
||||
}, AuthenticationUtil.getSystemUserName());
|
||||
|
||||
return normalized == null ? userId : normalized;
|
||||
}
|
||||
|
||||
private UserInfoAttrMapping initUserInfoAttrMapping(IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
return new UserInfoAttrMapping(identityServiceFacade.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(),
|
||||
identityServiceConfig.getFirstNameAttribute(),
|
||||
identityServiceConfig.getLastNameAttribute(),
|
||||
identityServiceConfig.getEmailAttribute());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,181 +1,181 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.Optional;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.OIDCUserInfo;
|
||||
|
||||
/**
|
||||
* A {@link RemoteUserMapper} implementation that detects and validates JWTs issued by the Alfresco Identity Service.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, ActivateableBean
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(IdentityServiceRemoteUserMapper.class);
|
||||
|
||||
/** Is the mapper enabled */
|
||||
private boolean isEnabled;
|
||||
|
||||
/** Are token validation failures handled silently? */
|
||||
private boolean isValidationFailureSilent;
|
||||
|
||||
private BearerTokenResolver bearerTokenResolver;
|
||||
|
||||
private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
|
||||
|
||||
/**
|
||||
* Sets the active flag
|
||||
*
|
||||
* @param isEnabled
|
||||
* true to enable the subsystem
|
||||
*/
|
||||
public void setActive(boolean isEnabled)
|
||||
{
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether token validation failures are silent
|
||||
*
|
||||
* @param silent
|
||||
* true to silently fail, false to throw an exception
|
||||
*/
|
||||
public void setValidationFailureSilent(boolean silent)
|
||||
{
|
||||
this.isValidationFailureSilent = silent;
|
||||
}
|
||||
|
||||
public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver)
|
||||
{
|
||||
this.bearerTokenResolver = bearerTokenResolver;
|
||||
}
|
||||
|
||||
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
|
||||
{
|
||||
this.jitProvisioningHandler = jitProvisioningHandler;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
*
|
||||
* @see org.alfresco.web.app.servlet.RemoteUserMapper#getRemoteUser(jakarta.servlet.http.HttpServletRequest) */
|
||||
@Override
|
||||
public String getRemoteUser(HttpServletRequest request)
|
||||
{
|
||||
LOGGER.trace("Retrieving username from http request...");
|
||||
|
||||
if (!this.isEnabled)
|
||||
{
|
||||
LOGGER.debug("IdentityServiceRemoteUserMapper is disabled, returning null.");
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
String normalizedUserId = extractUserFromHeader(request);
|
||||
|
||||
if (normalizedUserId != null)
|
||||
{
|
||||
// Normalize the user ID taking into account case sensitivity settings
|
||||
LOGGER.trace("Returning userId: " + AuthenticationUtil.maskUsername(normalizedUserId));
|
||||
return normalizedUserId;
|
||||
}
|
||||
}
|
||||
catch (IdentityServiceFacadeException e)
|
||||
{
|
||||
if (!isValidationFailureSilent)
|
||||
{
|
||||
throw new AuthenticationException("Failed to extract username from token: " + e.getMessage(), e);
|
||||
}
|
||||
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
|
||||
}
|
||||
LOGGER.trace("Could not identify a userId. Returning null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
*
|
||||
* @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive() */
|
||||
public boolean isActive()
|
||||
{
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the user name from the JWT in the given request.
|
||||
*
|
||||
* @param request
|
||||
* The request containing the JWT
|
||||
* @return The username or null if it can not be determined
|
||||
*/
|
||||
private String extractUserFromHeader(HttpServletRequest request)
|
||||
{
|
||||
// try authenticating with bearer token first
|
||||
LOGGER.debug("Trying bearer token...");
|
||||
|
||||
final String bearerToken;
|
||||
try
|
||||
{
|
||||
bearerToken = bearerTokenResolver.resolve(request);
|
||||
}
|
||||
catch (OAuth2AuthenticationException e)
|
||||
{
|
||||
LOGGER.debug("Failed to resolve Bearer token.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
final Optional<String> possibleUsername = jitProvisioningHandler
|
||||
.extractUserInfoAndCreateUserIfNeeded(bearerToken)
|
||||
.map(OIDCUserInfo::username);
|
||||
|
||||
if (possibleUsername.isEmpty())
|
||||
{
|
||||
LOGGER.debug("User could not be authenticated by IdentityServiceRemoteUserMapper.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedUsername = possibleUsername.get();
|
||||
LOGGER.trace("Extracted username: " + AuthenticationUtil.maskUsername(normalizedUsername));
|
||||
|
||||
return normalizedUsername;
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||
|
||||
/**
|
||||
* A {@link RemoteUserMapper} implementation that detects and validates JWTs
|
||||
* issued by the Alfresco Identity Service.
|
||||
*
|
||||
* @author Gavin Cornwell
|
||||
*/
|
||||
public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, ActivateableBean
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(IdentityServiceRemoteUserMapper.class);
|
||||
|
||||
/** Is the mapper enabled */
|
||||
private boolean isEnabled;
|
||||
|
||||
/** Are token validation failures handled silently? */
|
||||
private boolean isValidationFailureSilent;
|
||||
|
||||
private BearerTokenResolver bearerTokenResolver;
|
||||
|
||||
private IdentityServiceJITProvisioningHandler jitProvisioningHandler;
|
||||
|
||||
/**
|
||||
* Sets the active flag
|
||||
*
|
||||
* @param isEnabled true to enable the subsystem
|
||||
*/
|
||||
public void setActive(boolean isEnabled)
|
||||
{
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether token validation failures are silent
|
||||
*
|
||||
* @param silent true to silently fail, false to throw an exception
|
||||
*/
|
||||
public void setValidationFailureSilent(boolean silent)
|
||||
{
|
||||
this.isValidationFailureSilent = silent;
|
||||
}
|
||||
|
||||
public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver)
|
||||
{
|
||||
this.bearerTokenResolver = bearerTokenResolver;
|
||||
}
|
||||
|
||||
public void setJitProvisioningHandler(IdentityServiceJITProvisioningHandler jitProvisioningHandler)
|
||||
{
|
||||
this.jitProvisioningHandler = jitProvisioningHandler;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.web.app.servlet.RemoteUserMapper#getRemoteUser(jakarta.servlet.http.HttpServletRequest)
|
||||
*/
|
||||
@Override
|
||||
public String getRemoteUser(HttpServletRequest request)
|
||||
{
|
||||
LOGGER.trace("Retrieving username from http request...");
|
||||
|
||||
if (!this.isEnabled)
|
||||
{
|
||||
LOGGER.debug("IdentityServiceRemoteUserMapper is disabled, returning null.");
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
String normalizedUserId = extractUserFromHeader(request);
|
||||
|
||||
|
||||
if (normalizedUserId != null)
|
||||
{
|
||||
// Normalize the user ID taking into account case sensitivity settings
|
||||
LOGGER.trace("Returning userId: " + AuthenticationUtil.maskUsername(normalizedUserId));
|
||||
return normalizedUserId;
|
||||
}
|
||||
}
|
||||
catch (IdentityServiceFacadeException e)
|
||||
{
|
||||
if (!isValidationFailureSilent)
|
||||
{
|
||||
throw new AuthenticationException("Failed to extract username from token: " + e.getMessage(), e);
|
||||
}
|
||||
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
LOGGER.error("Failed to authenticate user using IdentityServiceRemoteUserMapper: " + e.getMessage(), e);
|
||||
}
|
||||
LOGGER.trace("Could not identify a userId. Returning null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive()
|
||||
*/
|
||||
public boolean isActive()
|
||||
{
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the user name from the JWT in the given request.
|
||||
*
|
||||
* @param request The request containing the JWT
|
||||
* @return The username or null if it can not be determined
|
||||
*/
|
||||
private String extractUserFromHeader(HttpServletRequest request)
|
||||
{
|
||||
// try authenticating with bearer token first
|
||||
LOGGER.debug("Trying bearer token...");
|
||||
|
||||
final String bearerToken;
|
||||
try
|
||||
{
|
||||
bearerToken = bearerTokenResolver.resolve(request);
|
||||
}
|
||||
catch (OAuth2AuthenticationException e)
|
||||
{
|
||||
LOGGER.debug("Failed to resolve Bearer token.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
final Optional<String> possibleUsername = jitProvisioningHandler
|
||||
.extractUserInfoAndCreateUserIfNeeded(bearerToken)
|
||||
.map(OIDCUserInfo::username);
|
||||
|
||||
if (possibleUsername.isEmpty())
|
||||
{
|
||||
LOGGER.debug("User could not be authenticated by IdentityServiceRemoteUserMapper.");
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalizedUsername = possibleUsername.get();
|
||||
LOGGER.trace("Extracted username: " + AuthenticationUtil.maskUsername(normalizedUsername));
|
||||
|
||||
return normalizedUsername;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.user;
|
||||
package org.alfresco.repo.security.authentication.identityservice;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -30,12 +30,21 @@ import static java.util.Objects.requireNonNull;
|
||||
|
||||
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceMetadataKey.AUDIENCE;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import com.nimbusds.oauth2.sdk.ErrorObject;
|
||||
import com.nimbusds.oauth2.sdk.ParseException;
|
||||
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.LogFactory;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
@@ -50,35 +59,27 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRe
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.DecodedTokenUser;
|
||||
import org.alfresco.repo.security.authentication.identityservice.user.UserInfoAttrMapping;
|
||||
|
||||
class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
|
||||
{
|
||||
private static final Log LOGGER = LogFactory.getLog(SpringBasedIdentityServiceFacade.class);
|
||||
private static final Instant SOME_INSIGNIFICANT_DATE_IN_THE_PAST = Instant.MIN.plusSeconds(12345);
|
||||
private final Map<AuthorizationGrantType, OAuth2AccessTokenResponseClient> clients;
|
||||
private final DefaultOAuth2UserService defaultOAuth2UserService;
|
||||
private final ClientRegistration clientRegistration;
|
||||
private final JwtDecoder jwtDecoder;
|
||||
|
||||
@@ -92,7 +93,6 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
|
||||
AuthorizationGrantType.AUTHORIZATION_CODE, createAuthorizationCodeClient(restOperations),
|
||||
AuthorizationGrantType.REFRESH_TOKEN, createRefreshTokenClient(restOperations),
|
||||
AuthorizationGrantType.PASSWORD, createPasswordClient(restOperations, clientRegistration));
|
||||
this.defaultOAuth2UserService = createOAuth2UserService(restOperations);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -121,18 +121,51 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DecodedTokenUser> getUserInfo(String token, UserInfoAttrMapping userInfoAttrMapping)
|
||||
public Optional<OIDCUserInfo> getUserInfo(String tokenParameter, String principalAttribute)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Optional.ofNullable(defaultOAuth2UserService.loadUser(new OAuth2UserRequest(clientRegistration, getSpringAccessToken(token))))
|
||||
.flatMap(oAuth2User -> mapOAuth2UserToDecodedTokenUser(oAuth2User, userInfoAttrMapping));
|
||||
}
|
||||
catch (OAuth2AuthenticationException exception)
|
||||
{
|
||||
LOGGER.warn("User Info Request failed: " + exception.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.ofNullable(tokenParameter)
|
||||
.filter(Predicate.not(String::isEmpty))
|
||||
.flatMap(token -> Optional.ofNullable(clientRegistration)
|
||||
.map(ClientRegistration::getProviderDetails)
|
||||
.map(ClientRegistration.ProviderDetails::getUserInfoEndpoint)
|
||||
.map(ClientRegistration.ProviderDetails.UserInfoEndpoint::getUri)
|
||||
.flatMap(uri -> {
|
||||
try
|
||||
{
|
||||
return Optional.of(
|
||||
new UserInfoRequest(new URI(uri), new BearerAccessToken(token)).toHTTPRequest().send());
|
||||
}
|
||||
catch (IOException | URISyntaxException e)
|
||||
{
|
||||
LOGGER.warn("Failed to get user information. Reason: " + e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.flatMap(httpResponse -> {
|
||||
try
|
||||
{
|
||||
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
|
||||
@@ -169,7 +202,11 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
|
||||
|
||||
if (grant.isRefreshToken())
|
||||
{
|
||||
final OAuth2AccessToken expiredAccessToken = getSpringAccessToken("JUST_FOR_FULFILLING_THE_SPRING_API");
|
||||
final OAuth2AccessToken expiredAccessToken = new OAuth2AccessToken(
|
||||
TokenType.BEARER,
|
||||
"JUST_FOR_FULFILLING_THE_SPRING_API",
|
||||
SOME_INSIGNIFICANT_DATE_IN_THE_PAST,
|
||||
SOME_INSIGNIFICANT_DATE_IN_THE_PAST.plusSeconds(1));
|
||||
final OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(grant.getRefreshToken(), null);
|
||||
|
||||
return new OAuth2RefreshTokenGrantRequest(clientRegistration, expiredAccessToken, refreshToken,
|
||||
@@ -221,26 +258,6 @@ class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
|
||||
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,
|
||||
ClientRegistration clientRegistration)
|
||||
{
|
||||
@@ -271,16 +288,6 @@ 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 final OAuth2AccessTokenResponse tokenResponse;
|
||||
|
||||
@@ -37,19 +37,13 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.nimbusds.oauth2.sdk.Scope;
|
||||
import com.nimbusds.oauth2.sdk.id.Identifier;
|
||||
import com.nimbusds.oauth2.sdk.id.State;
|
||||
import 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.security.authentication.AuthenticationException;
|
||||
import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator;
|
||||
@@ -59,9 +53,16 @@ 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.AuthorizationException;
|
||||
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 or to initiate the OIDC authorization code flow.
|
||||
* An {@link AdminConsoleAuthenticator} implementation to extract an externally authenticated user ID
|
||||
* or to initiate the OIDC authorization code flow.
|
||||
*/
|
||||
public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAuthenticator, ActivateableBean
|
||||
{
|
||||
@@ -70,6 +71,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
|
||||
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
|
||||
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
|
||||
private static final Set<String> SCOPES = Set.of("openid", "profile", "email", "offline_access");
|
||||
|
||||
private IdentityServiceConfig identityServiceConfig;
|
||||
private IdentityServiceFacade identityServiceFacade;
|
||||
@@ -143,7 +145,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
try
|
||||
{
|
||||
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
|
||||
authorizationCode(code, request.getRequestURL().toString()));
|
||||
authorizationCode(code, request.getRequestURL().toString()));
|
||||
addCookies(response, accessTokenAuthorization);
|
||||
bearerToken = accessTokenAuthorization.getAccessToken().getTokenValue();
|
||||
}
|
||||
@@ -152,8 +154,8 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
if (LOGGER.isWarnEnabled())
|
||||
{
|
||||
LOGGER.warn(
|
||||
"Error while trying to retrieve a response using the Authorization Code at the Token Endpoint: {}",
|
||||
exception.getMessage());
|
||||
"Error while trying to retrieve a response using the Authorization Code at the Token Endpoint: {}",
|
||||
exception.getMessage());
|
||||
}
|
||||
}
|
||||
return bearerToken;
|
||||
@@ -186,7 +188,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
{
|
||||
cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken().getTokenValue(), response);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -196,13 +198,13 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
State state = new State();
|
||||
|
||||
UriComponentsBuilder authRequestBuilder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getAuthorizationUri())
|
||||
.queryParam("client_id", clientRegistration.getClientId())
|
||||
.queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString()))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", String.join("+", getScopes(clientRegistration)))
|
||||
.queryParam("state", state.toString());
|
||||
.queryParam("client_id", clientRegistration.getClientId())
|
||||
.queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString()))
|
||||
.queryParam("response_type", "code")
|
||||
.queryParam("scope", String.join("+", getScopes(clientRegistration)))
|
||||
.queryParam("state", state.toString());
|
||||
|
||||
if (StringUtils.isNotBlank(identityServiceConfig.getAudience()))
|
||||
if(StringUtils.isNotBlank(identityServiceConfig.getAudience()))
|
||||
{
|
||||
authRequestBuilder.queryParam("audience", identityServiceConfig.getAudience());
|
||||
}
|
||||
@@ -213,25 +215,20 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
private Set<String> getScopes(ClientRegistration clientRegistration)
|
||||
{
|
||||
return Optional.ofNullable(clientRegistration.getProviderDetails())
|
||||
.map(ProviderDetails::getConfigurationMetadata)
|
||||
.map(metadata -> metadata.get(SCOPES_SUPPORTED.getValue()))
|
||||
.filter(Scope.class::isInstance)
|
||||
.map(Scope.class::cast)
|
||||
.map(this::getSupportedScopes)
|
||||
.orElse(clientRegistration.getScopes());
|
||||
.map(ProviderDetails::getConfigurationMetadata)
|
||||
.map(metadata -> metadata.get(SCOPES_SUPPORTED.getValue()))
|
||||
.filter(Scope.class::isInstance)
|
||||
.map(Scope.class::cast)
|
||||
.map(this::getSupportedScopes)
|
||||
.orElse(clientRegistration.getScopes());
|
||||
}
|
||||
|
||||
private Set<String> getSupportedScopes(Scope scopes)
|
||||
{
|
||||
return scopes.stream()
|
||||
.filter(this::hasAdminConsoleScope)
|
||||
.map(Identifier::getValue)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean hasAdminConsoleScope(Scope.Value scope)
|
||||
{
|
||||
return identityServiceConfig.getAdminConsoleScopes().contains(scope.getValue());
|
||||
.filter(scope -> SCOPES.contains(scope.getValue()))
|
||||
.map(Identifier::getValue)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private String getRedirectUri(String requestURL)
|
||||
@@ -266,7 +263,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
private AccessTokenAuthorization doRefreshAuthToken(String refreshToken)
|
||||
{
|
||||
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
|
||||
AuthorizationGrant.refreshToken(refreshToken));
|
||||
AuthorizationGrant.refreshToken(refreshToken));
|
||||
if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null)
|
||||
{
|
||||
throw new AuthenticationException("AccessTokenResponse is null or empty");
|
||||
@@ -287,7 +284,7 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
}
|
||||
|
||||
public void setIdentityServiceFacade(
|
||||
IdentityServiceFacade identityServiceFacade)
|
||||
IdentityServiceFacade identityServiceFacade)
|
||||
{
|
||||
this.identityServiceFacade = identityServiceFacade;
|
||||
}
|
||||
@@ -298,13 +295,13 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
}
|
||||
|
||||
public void setCookiesService(
|
||||
AdminConsoleAuthenticationCookiesService cookiesService)
|
||||
AdminConsoleAuthenticationCookiesService cookiesService)
|
||||
{
|
||||
this.cookiesService = cookiesService;
|
||||
}
|
||||
|
||||
public void setIdentityServiceConfig(
|
||||
IdentityServiceConfig identityServiceConfig)
|
||||
IdentityServiceConfig identityServiceConfig)
|
||||
{
|
||||
this.identityServiceConfig = identityServiceConfig;
|
||||
}
|
||||
@@ -319,4 +316,4 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
||||
{
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.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));
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.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);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.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;
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Repository
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||
* %%
|
||||
* This file is part of the Alfresco software.
|
||||
* If the software was purchased under a paid Alfresco license, the terms of
|
||||
* the paid license agreement will prevail. Otherwise, the software is
|
||||
* provided under the following open source license terms:
|
||||
*
|
||||
* Alfresco is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Alfresco is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
* #L%
|
||||
*/
|
||||
package org.alfresco.repo.security.authentication.identityservice.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)
|
||||
{}
|
||||
@@ -28,17 +28,11 @@ package org.alfresco.repo.workflow.activiti.script;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.activiti.engine.RepositoryService;
|
||||
import org.activiti.engine.delegate.VariableScope;
|
||||
import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl;
|
||||
import org.activiti.engine.impl.context.Context;
|
||||
import org.activiti.engine.impl.el.Expression;
|
||||
import org.activiti.engine.impl.persistence.entity.DeploymentEntity;
|
||||
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
|
||||
import org.activiti.engine.repository.ProcessDefinition;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
||||
import org.alfresco.repo.workflow.WorkflowDeployer;
|
||||
@@ -51,12 +45,13 @@ import org.alfresco.service.cmr.security.PersonService;
|
||||
import org.alfresco.service.cmr.workflow.WorkflowException;
|
||||
|
||||
/**
|
||||
* Base class for execution scripts, using {@link ScriptService} as part of activiti workflow.
|
||||
* Base class for execution scripts, using {@link ScriptService} as part of
|
||||
* activiti workflow.
|
||||
*
|
||||
* @author Frederik Heremans
|
||||
* @since 3.4.e
|
||||
*/
|
||||
public class ActivitiScriptBase
|
||||
public class ActivitiScriptBase
|
||||
{
|
||||
protected static final String PERSON_BINDING_NAME = "person";
|
||||
protected static final String USERHOME_BINDING_NAME = "userhome";
|
||||
@@ -66,19 +61,17 @@ public class ActivitiScriptBase
|
||||
protected Expression runAs;
|
||||
protected Expression scriptProcessor;
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ActivitiScriptBase.class);
|
||||
|
||||
protected Object executeScript(String theScript, Map<String, Object> model, String scriptProcessorName, String runAsUser)
|
||||
{
|
||||
String user = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
|
||||
|
||||
Object scriptResult = null;
|
||||
if (runAsUser == null && user != null)
|
||||
{
|
||||
// Just execute the script using the current user
|
||||
scriptResult = executeScript(theScript, model, scriptProcessorName);
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (runAsUser != null)
|
||||
{
|
||||
@@ -94,25 +87,26 @@ public class ActivitiScriptBase
|
||||
}
|
||||
return scriptResult;
|
||||
}
|
||||
|
||||
|
||||
protected Object executeScriptAsUser(final String theScript, final Map<String, Object> model, final String scriptProcessorName, final String runAsUser)
|
||||
{
|
||||
// execute as specified runAsUser
|
||||
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<>() {
|
||||
return AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>()
|
||||
{
|
||||
public Object doWork() throws Exception
|
||||
{
|
||||
return executeScript(theScript, model, scriptProcessorName);
|
||||
}
|
||||
}, runAsUser);
|
||||
}
|
||||
|
||||
|
||||
protected Object executeScript(String theScript, Map<String, Object> model, String scriptProcessorName)
|
||||
{
|
||||
// Execute the script using the appropriate processor
|
||||
Object scriptResult = null;
|
||||
|
||||
// Checks if current workflow is secure
|
||||
boolean secure = isSecure(model);
|
||||
boolean secure = isSecure();
|
||||
|
||||
if (scriptProcessorName != null)
|
||||
{
|
||||
@@ -123,11 +117,11 @@ public class ActivitiScriptBase
|
||||
// Use default script-processor
|
||||
scriptResult = getServiceRegistry().getScriptService().executeScriptString(theScript, model, secure);
|
||||
}
|
||||
|
||||
|
||||
return scriptResult;
|
||||
}
|
||||
|
||||
protected String getStringValue(Expression expression, VariableScope scope)
|
||||
|
||||
protected String getStringValue(Expression expression, VariableScope scope)
|
||||
{
|
||||
if (expression != null)
|
||||
{
|
||||
@@ -139,15 +133,15 @@ public class ActivitiScriptBase
|
||||
protected ServiceRegistry getServiceRegistry()
|
||||
{
|
||||
ProcessEngineConfigurationImpl config = Context.getProcessEngineConfiguration();
|
||||
if (config != null)
|
||||
if (config != null)
|
||||
{
|
||||
// Fetch the registry that is injected in the activiti spring-configuration
|
||||
ServiceRegistry registry = (ServiceRegistry) config.getBeans().get(ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
|
||||
if (registry == null)
|
||||
{
|
||||
throw new RuntimeException(
|
||||
"Service-registry not present in ProcessEngineConfiguration beans, expected ServiceRegistry with key" +
|
||||
ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
|
||||
"Service-registry not present in ProcessEngineConfiguration beans, expected ServiceRegistry with key" +
|
||||
ActivitiConstants.SERVICE_REGISTRY_BEAN_KEY);
|
||||
}
|
||||
return registry;
|
||||
}
|
||||
@@ -155,136 +149,42 @@ public class ActivitiScriptBase
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the workflow must be considered secure or not - based on {@link DeploymentEntity} category. If it is not considered secure, the workflow will be executed in sandbox context with more restrictions
|
||||
* Checks whether the workflow must be considered secure or not - based on {@link DeploymentEntity} category.
|
||||
* If it is not considered secure, the workflow will be executed in sandbox context with more restrictions
|
||||
*
|
||||
* @return true if workflow is considered secure, false otherwise
|
||||
*/
|
||||
private boolean isSecure(Map<String, Object> model)
|
||||
{
|
||||
String category = getDeploymentCategory(model);
|
||||
|
||||
// iF The deployment category matches the condition (either internal or full access) the workflow is considered secure
|
||||
return category != null && (WorkflowDeployer.CATEGORY_ALFRESCO_INTERNAL.equals(category) || WorkflowDeployer.CATEGORY_FULL_ACCESS.equals(category));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the deployment category from the execution context. If no execution context is available, a query to obtain the deployment is performed so the category can be returned.
|
||||
*
|
||||
* @param model
|
||||
* a map with workflow model
|
||||
* @return the deployment category
|
||||
*/
|
||||
private String getDeploymentCategory(Map<String, Object> model)
|
||||
{
|
||||
String category = getDeploymentCategoryFromContext();
|
||||
|
||||
if (category == null)
|
||||
{
|
||||
String deploymentId = null;
|
||||
String processDefinitionId = null;
|
||||
|
||||
if (model != null && model.containsKey(EXECUTION_BINDING_NAME) && model.get(EXECUTION_BINDING_NAME) instanceof ExecutionEntity)
|
||||
{
|
||||
ExecutionEntity executionEntity = (ExecutionEntity) model.get(EXECUTION_BINDING_NAME);
|
||||
deploymentId = executionEntity.getDeploymentId();
|
||||
processDefinitionId = executionEntity.getProcessDefinitionId();
|
||||
}
|
||||
|
||||
category = getDeploymentCategoryFromQuery(deploymentId, processDefinitionId);
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the deployment category from current execution context
|
||||
*
|
||||
* @return the category for current execution deployment, otherwise null
|
||||
*/
|
||||
private String getDeploymentCategoryFromContext()
|
||||
private boolean isSecure()
|
||||
{
|
||||
String category = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (Context.isExecutionContextActive())
|
||||
{
|
||||
category = Context.getExecutionContext().getDeployment().getCategory();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("No execution context available");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Could not obtain deployment category from execution context: {}", e.getMessage());
|
||||
}
|
||||
// No action required
|
||||
}
|
||||
|
||||
return category;
|
||||
// If the workflow is considered secure, the deployment entity category matches the condition (either internal or full access)
|
||||
return category != null && (WorkflowDeployer.CATEGORY_ALFRESCO_INTERNAL.equals(category) || WorkflowDeployer.CATEGORY_FULL_ACCESS.equals(category));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the deployment category through a query
|
||||
*
|
||||
* @param deploymentId
|
||||
* the deployment id to obtain the category from
|
||||
* @param processDefinitionId
|
||||
* if no deployment id is provided, the process definition id can be used to obtain the deployment
|
||||
* @return the category for the obtained deployment, otherwise null
|
||||
* Checks that the specified 'runAs' field
|
||||
* specifies a valid username.
|
||||
*/
|
||||
private String getDeploymentCategoryFromQuery(String deploymentId, String processDefinitionId)
|
||||
private void validateRunAsUser(final String runAsUser)
|
||||
{
|
||||
String category = null;
|
||||
|
||||
try
|
||||
Boolean runAsExists = AuthenticationUtil.runAs(new RunAsWork<Boolean>()
|
||||
{
|
||||
RepositoryService repositoryService = Context.getProcessEngineConfiguration().getRepositoryService();
|
||||
|
||||
if (deploymentId == null && processDefinitionId != null)
|
||||
{
|
||||
ProcessDefinition processDefnition = repositoryService.getProcessDefinition(processDefinitionId);
|
||||
if (processDefnition != null)
|
||||
{
|
||||
deploymentId = processDefnition.getDeploymentId();
|
||||
}
|
||||
}
|
||||
|
||||
if (deploymentId != null)
|
||||
{
|
||||
DeploymentEntity deployment = (DeploymentEntity) repositoryService.createDeploymentQuery().deploymentId(deploymentId).singleResult();
|
||||
if (deployment != null)
|
||||
{
|
||||
category = deployment.getCategory();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (LOGGER.isDebugEnabled())
|
||||
{
|
||||
LOGGER.debug("Could not obtain deployment category through a query: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the specified 'runAs' field specifies a valid username.
|
||||
*/
|
||||
private void validateRunAsUser(final String runAsUser)
|
||||
{
|
||||
Boolean runAsExists = AuthenticationUtil.runAs(new RunAsWork<>() {
|
||||
// Validate using System user to ensure sufficient permissions available to access person node.
|
||||
|
||||
@Override
|
||||
public Boolean doWork() throws Exception
|
||||
public Boolean doWork() throws Exception
|
||||
{
|
||||
return getServiceRegistry().getPersonService().personExists(runAsUser);
|
||||
}
|
||||
@@ -295,21 +195,21 @@ public class ActivitiScriptBase
|
||||
throw new WorkflowException("runas user '" + runAsUser + "' does not exist.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected ActivitiScriptNode getPersonNode(String runAsUser)
|
||||
{
|
||||
String userName = null;
|
||||
if (runAsUser != null)
|
||||
if (runAsUser != null)
|
||||
{
|
||||
userName = runAsUser;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
userName = AuthenticationUtil.getFullyAuthenticatedUser();
|
||||
}
|
||||
|
||||
|
||||
// The "System" user is a special case, which has no person object associated with it.
|
||||
if (userName != null && !AuthenticationUtil.SYSTEM_USER_NAME.equals(userName))
|
||||
if(userName != null && !AuthenticationUtil.SYSTEM_USER_NAME.equals(userName))
|
||||
{
|
||||
ServiceRegistry services = getServiceRegistry();
|
||||
PersonService personService = services.getPersonService();
|
||||
@@ -321,18 +221,18 @@ public class ActivitiScriptBase
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setScript(Expression script)
|
||||
|
||||
public void setScript(Expression script)
|
||||
{
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
public void setRunAs(Expression runAs)
|
||||
public void setRunAs(Expression runAs)
|
||||
{
|
||||
this.runAs = runAs;
|
||||
}
|
||||
|
||||
public void setScriptProcessor(Expression scriptProcessor)
|
||||
public void setScriptProcessor(Expression scriptProcessor)
|
||||
{
|
||||
this.scriptProcessor = scriptProcessor;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Vlastnost ''{0}'' nebyla nastavena: {1} ({2})
|
||||
system.err.duplicate_name=Duplicitn\u00ed n\u00e1zvy pod\u0159\u00edzen\u00fdch objekt\u016f nejsou povoleny ({0})
|
||||
system.err.lucene_not_supported=Subsyst\u00e9m hled\u00e1n\u00ed Lucene nen\u00ed podporov\u00e1n. Viz https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=Subsyst\u00e9m hled\u00e1n\u00ed Lucene nen\u00ed podporov\u00e1n. Viz http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Egenskaben ''{0}'' er ikke blevet indstillet: {1} ({2})
|
||||
system.err.duplicate_name=Duplikeret navn p\u00e5 underordnet er ikke tilladt: {0}
|
||||
system.err.lucene_not_supported=Lucene-s\u00f8geundersystemet underst\u00f8ttes ikke. Se https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=Lucene-s\u00f8geundersystemet underst\u00f8ttes ikke. Se http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Ominaisuutta {0} ei ole m\u00e4\u00e4ritetty: {1} ({2})
|
||||
system.err.duplicate_name=P\u00e4\u00e4llekk\u00e4ist\u00e4 alatasonime\u00e4 ei sallita: {0}
|
||||
system.err.lucene_not_supported=Lucene-hakualij\u00e4rjestelm\u00e4\u00e4 ei tueta. Saat lis\u00e4tietoja osoitteesta https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=Lucene-hakualij\u00e4rjestelm\u00e4\u00e4 ei tueta. Saat lis\u00e4tietoja osoitteesta http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set : {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed : {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Nie ustawiono w\u0142a\u015bciwo\u015bci ''{0}'': {1} ({2})
|
||||
system.err.duplicate_name=Zduplikowane nazwy element\u00f3w podrz\u0119dnych s\u0105 niedozwolone: {0}
|
||||
system.err.lucene_not_supported=Podsystem wyszukiwania Lucene nie jest obs\u0142ugiwany. Zobacz https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=Podsystem wyszukiwania Lucene nie jest obs\u0142ugiwany. Zobacz http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
system.err.property_not_set=Property ''{0}'' has not been set: {1} ({2})
|
||||
system.err.duplicate_name=Duplicate child name not allowed: {0}
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see https://support.hyland.com/p/alfresco
|
||||
system.err.lucene_not_supported=The lucene search subsystem is not supported. Please see http://docs.alfresco.com/{0}/tasks/lucene-solr4-migration.html
|
||||
|
||||
# Bootstrap configuration check messages
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user