Compare commits

..

1 Commits

Author SHA1 Message Date
Damian Ujma
d31842352d Link [skip ci] 2025-02-10 12:51:00 +01:00
134 changed files with 14194 additions and 14597 deletions

View File

@@ -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: |

View File

@@ -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 }}

View File

@@ -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"
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();

View File

@@ -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)
***

View File

@@ -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.
***

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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>

View File

@@ -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.");

View File

@@ -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
*/

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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.

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
*/

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
View File

@@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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>

View File

@@ -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>

View File

@@ -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)
{}

View File

@@ -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) +
']';
}
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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
***

View File

@@ -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
***

View File

@@ -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>

View File

@@ -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"));
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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)
{}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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