Compare commits

..

52 Commits

Author SHA1 Message Date
alfresco-build
73ef1ed9ff [maven-release-plugin][skip ci] prepare release 25.3.0.40 2025-09-22 09:11:35 +00:00
Gerard Olenski
d3bc9e2b60 ACS-10159 Bump ATS (#3580) 2025-09-22 10:12:24 +02:00
alfresco-build
174186d1ff [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-21 00:09:21 +00:00
alfresco-build
57331afe8f [maven-release-plugin][skip ci] prepare release 25.3.0.39 2025-09-21 00:09:18 +00:00
Alfresco CI User
7c87595b0c [force] Force release for 2025-09-21. 2025-09-21 00:05:02 +00:00
alfresco-build
b7191b175e [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-17 09:27:11 +00:00
alfresco-build
39b19d1ceb [maven-release-plugin][skip ci] prepare release 25.3.0.38 2025-09-17 09:27:09 +00:00
Kacper Magdziarz
9e23b99078 [ACS-9736] Add new bean for intercepting current audit entries. (#3402)
This introduces a new audit record reporting mechanism for intercepting current audit entries. The implementation adds the ability to report audit data to audit storage in addition to the traditional database storage.

Key changes:

- Added new audit record classes and utilities for structured audit data handling
- Introduced configurable audit destination settings (database vs audit storage)
- Created audit record reporter interface and implementation
2025-09-17 10:40:32 +02:00
alfresco-build
ac36ac07e8 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-16 08:59:50 +00:00
alfresco-build
cc10339577 [maven-release-plugin][skip ci] prepare release 25.3.0.37 2025-09-16 08:59:48 +00:00
SanjoyHyland2025
8aa975fbc3 Merge pull request #3571 from Alfresco/fix/MNT-25186_category_tags_filter_resolve
MNT-25186: fixed the tags and category filters for elasticsearch
2025-09-16 13:44:57 +05:30
alfresco-build
01620b75ff [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-14 00:09:07 +00:00
alfresco-build
9327218f17 [maven-release-plugin][skip ci] prepare release 25.3.0.36 2025-09-14 00:09:04 +00:00
Alfresco CI User
b57373fbe3 [force] Force release for 2025-09-14. 2025-09-14 00:04:51 +00:00
Sanjoy Das
613fb458b9 fix build failed 2025-09-12 14:25:05 +05:30
Sanjoy Das
31cd97b9d2 MNT-25186: fixed the tags and category filters for elasticsearch 2025-09-12 11:11:28 +05:30
alfresco-build
255fe46c8e [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-11 11:54:10 +00:00
alfresco-build
88d32748b1 [maven-release-plugin][skip ci] prepare release 25.3.0.35 2025-09-11 11:54:08 +00:00
Eva Vasques
90e1522a56 ACS-10042 - AGS Concurrent IPR Group Creation (#3566) 2025-09-11 12:05:48 +01:00
alfresco-build
7e61befc21 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-07 00:07:46 +00:00
alfresco-build
be6dc14330 [maven-release-plugin][skip ci] prepare release 25.3.0.34 2025-09-07 00:07:45 +00:00
Alfresco CI User
ab4bc1af9f [force] Force release for 2025-09-07. 2025-09-07 00:04:37 +00:00
alfresco-build
eaec23ae7a [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-03 18:24:45 +00:00
alfresco-build
8ff727c95d [maven-release-plugin][skip ci] prepare release 25.3.0.33 2025-09-03 18:24:43 +00:00
dependabot[bot]
48475fdfaa Bump actions/checkout from 4 to 5 (#3560)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-01 09:21:55 +02:00
alfresco-build
812959be2e [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-28 12:48:53 +00:00
alfresco-build
5b8c52db67 [maven-release-plugin][skip ci] prepare release 25.3.0.32 2025-08-28 12:48:51 +00:00
Debjit Chattopadhyay
20c42b6561 MNT-24308 : fix search filterquery creation for displaying favourites for both Solr and Elasticsearch
MNT-24308 : On click of 'My Favorites' link under 'Documents' in the left panel of 'Document Library' page, all the links appear grayed out
2025-08-28 16:54:32 +05:30
Debjit Chattopadhyay
52dfea9b21 cleaning up extra loggers 2025-08-28 16:09:42 +05:30
alfresco-build
d04dada44e [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-28 09:55:46 +00:00
alfresco-build
2e85de7c81 [maven-release-plugin][skip ci] prepare release 25.3.0.31 2025-08-28 09:55:44 +00:00
Tiago Salvado
42324368e5 [MNT-25242] Add property to control if scope is cleaned (#3520) 2025-08-28 10:13:31 +01:00
alfresco-build
8d885220d8 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-28 06:17:04 +00:00
alfresco-build
6367f5304d [maven-release-plugin][skip ci] prepare release 25.3.0.30 2025-08-28 06:17:02 +00:00
Debjit Chattopadhyay
f17b309c27 On click of 'My Favorites' link under 'Documents' in the left panel of 'Document Library' page, all the links appear grayed out 2025-08-27 18:46:24 +05:30
Belal Ansari
bb2cc1765d MNT-25216 Error on fixedAclUpdaterJobDetail execution when using Oracle (#3521) 2025-08-27 15:28:54 +05:30
alfresco-build
d20e8ee158 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-27 04:57:42 +00:00
alfresco-build
254193f9aa [maven-release-plugin][skip ci] prepare release 25.3.0.29 2025-08-27 04:57:40 +00:00
SatyamSah5
4293f21618 [ACS-9991] added logic to execute text extraction for content indexing even if thumbnails are disabled. (#3516) 2025-08-27 09:44:02 +05:30
alfresco-build
e0eb43c479 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-26 12:39:02 +00:00
alfresco-build
f1bbb6cce7 [maven-release-plugin][skip ci] prepare release 25.3.0.28 2025-08-26 12:39:00 +00:00
cezary-witkowski
e6e2a2d8ac [ACS-9933] Use only lang3 3.18 (#3544)
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
2025-08-26 13:56:50 +02:00
alfresco-build
c2cfcdc35a [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-25 09:30:32 +00:00
alfresco-build
6b2ac86b1d [maven-release-plugin][skip ci] prepare release 25.3.0.27 2025-08-25 09:30:30 +00:00
Arindam Roy
8b212dc4cf [ACS-10053] Bump ATS to 4.2.1 (#3543) 2025-08-25 14:18:05 +05:30
alfresco-build
e2c357c1e0 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-24 00:08:14 +00:00
alfresco-build
4a024e510d [maven-release-plugin][skip ci] prepare release 25.3.0.26 2025-08-24 00:08:12 +00:00
Alfresco CI User
3f75c9b15f [force] Force release for 2025-08-24. 2025-08-24 00:05:05 +00:00
alfresco-build
96f94a98be [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-21 18:08:07 +00:00
alfresco-build
bfc0445aeb [maven-release-plugin][skip ci] prepare release 25.3.0.25 2025-08-21 18:08:05 +00:00
Debjit Chattopadhyay
977f6f12d4 Fix for "MNT-24776 : Category Picker Error when a User does not have Read Permissions to a Category"
Fix for "MNT-24776 : Category Picker Error when a User does not have Read Permissions to a Category"
2025-08-21 22:41:38 +05:30
Debjit Chattopadhyay
626640ddc7 Fix for "MNT-24776 : Category Picker Error when a User does not have Read Permissions to a Category" 2025-08-19 17:35:00 +05:30
65 changed files with 1112 additions and 163 deletions

View File

@@ -38,7 +38,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
@@ -61,7 +61,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -84,7 +84,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -173,7 +173,7 @@ jobs:
testModule: mmt
testAttributes: "-Dtest=AllMmtUnitTestSuite"
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -210,7 +210,7 @@ jobs:
env:
REQUIRES_INSTALLED_ARTIFACTS: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -245,7 +245,7 @@ jobs:
matrix:
version: ['10.5', '10.6']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -272,7 +272,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -299,7 +299,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -325,7 +325,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -351,7 +351,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -377,7 +377,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -401,7 +401,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -457,7 +457,7 @@ jobs:
disabledHostnameVerification: false
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: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -527,7 +527,7 @@ jobs:
env:
REQUIRES_LOCAL_IMAGES: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -566,7 +566,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -596,7 +596,7 @@ jobs:
env:
REQUIRES_INSTALLED_ARTIFACTS: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -628,7 +628,7 @@ jobs:
env:
REQUIRES_INSTALLED_ARTIFACTS: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -656,7 +656,7 @@ jobs:
env:
REQUIRES_LOCAL_IMAGES: true
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1
@@ -702,7 +702,7 @@ jobs:
!contains(github.event.head_commit.message, '[skip tests]') &&
!contains(github.event.head_commit.message, '[force]')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/free-hosted-runner-disk-space@v8.24.1
- uses: Alfresco/alfresco-build-tools/.github/actions/setup-java-build@v8.24.1

View File

@@ -31,7 +31,7 @@ jobs:
!contains(github.event.head_commit.message, '[no release]') &&
github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1
@@ -60,7 +60,7 @@ jobs:
!contains(github.event.head_commit.message, '[no downstream]') &&
github.event_name != 'pull_request'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v8.24.1

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
if: contains(github.event.head_commit.message, '[reformat code]')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:

View File

@@ -1242,7 +1242,7 @@
"filename": "repository/src/main/resources/alfresco/repository.properties",
"hashed_secret": "1459a56410378e4d3ab470eff570e5eae1742762",
"is_verified": false,
"line_number": 312,
"line_number": 314,
"is_secret": false
},
{
@@ -1250,7 +1250,7 @@
"filename": "repository/src/main/resources/alfresco/repository.properties",
"hashed_secret": "84551ae5442affc9f1a2d3b4c86ae8b24860149d",
"is_verified": false,
"line_number": 771,
"line_number": 773,
"is_secret": false
}
],
@@ -1845,5 +1845,5 @@
}
]
},
"generated_at": "2025-06-09T16:43:14Z"
"generated_at": "2025-07-23T08:25:11Z"
}

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
<version>25.3.0.25-SNAPSHOT</version>
<version>25.3.0.40</version>
</parent>
<build>
@@ -88,7 +88,7 @@
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>3.6.0</version>
<version>3.4.0</version>
<exclusions>
<exclusion>
<groupId>org.bouncycastle</groupId>

View File

@@ -43,7 +43,7 @@ import com.github.dockerjava.core.command.LogContainerResultCallback;
import com.github.dockerjava.netty.NettyDockerCmdExecFactory;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang.SystemUtils;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -37,7 +37,7 @@ import static org.alfresco.rest.rm.community.util.CommonTestUtils.generateTestPr
import java.util.Collections;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.annotations.Test;
@@ -56,7 +56,7 @@ import org.alfresco.test.AlfrescoTest;
/**
* Add Relationship tests
*
*
* @author Kavit Shah
*/
public class AddRelationshipTests extends BaseRMRestTest

View File

@@ -45,7 +45,7 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;

View File

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

View File

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

View File

@@ -36,6 +36,7 @@ import java.util.Set;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.extensions.webscripts.ui.common.StringUtils;
import org.alfresco.model.ContentModel;
@@ -649,8 +650,8 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
}
catch (DuplicateChildNodeNameException ex)
{
// the group was concurrently created
group = authorityService.getName(AuthorityType.GROUP, groupShortName);
// Rethrow as ConcurrencyFailureException so that is can be retried and linked to the group created by the concurrent transaction
throw new ConcurrencyFailureException("IPR group creation failed due to concurrent duplicate group name creation: " + groupShortName);
}
return group;

View File

@@ -29,14 +29,23 @@ package org.alfresco.module.org_alfresco_module_rm.test.legacy.service;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import org.junit.Assert;
import org.springframework.dao.ConcurrencyFailureException;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.util.GUID;
@@ -206,7 +215,8 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
final NodeRef record = doTestInTransaction(new Test<NodeRef>() {
public NodeRef run() throws Exception
{
NodeRef record = fileFolderService.create(documentLibrary, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef();
NodeRef record = fileFolderService.create(documentLibrary, GUID.generate(), ContentModel.TYPE_CONTENT)
.getNodeRef();
recordService.createRecord(filePlan, record);
return record;
}
@@ -279,4 +289,238 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
}
});
}
public void testConcurrentSetWithRetry()
{
Set<String> extendedReaders = new HashSet<>(2);
Set<String> extendedWriters = new HashSet<>(2);
Set<NodeRef> documents = setupConcurrentTestCase(10, extendedReaders, extendedWriters);
// For each record created previously, spawn a thread to set extended security so we cause concurrency
// failure trying to create IPR groups with the same name
fireParallelExecutionOfSetExtendedSecurity(documents, extendedReaders, extendedWriters, true);
// Look for duplicated IPR groups and verify all documents have the same groups assigned
verifyCreatedGroups(documents, false);
AuthenticationUtil.clearCurrentSecurityContext();
}
public void testConcurrentSetWithoutRetry()
{
Set<String> extendedReaders = new HashSet<>(2);
Set<String> extendedWriters = new HashSet<>(2);
Set<NodeRef> documents = setupConcurrentTestCase(10, extendedReaders, extendedWriters);
// For each record created previously, spawn a thread to set extended security so we cause concurrency
// failure trying to create IPR groups with the same name.
// Since there is no retry, we expect to get a ConcurrencyFailureException
Assert.assertThrows(ConcurrencyFailureException.class, () -> {
fireParallelExecutionOfSetExtendedSecurity(documents, extendedReaders, extendedWriters, false);
});
// Look for duplicated IPR groups and verify all documents have the same groups assigned
// Since there was a ConcurrencyFailureException some threads failed to set extended security so some
// documents may not have IPR groups created.
verifyCreatedGroups(documents, true);
AuthenticationUtil.clearCurrentSecurityContext();
}
private Set<NodeRef> setupConcurrentTestCase(int concurrentThreads, Set<String> extendedReaders, Set<String> extendedWriters)
{
final String usera = createTestUser();
final String userb = createTestUser();
final String owner = createTestUser();
extendedReaders.add(usera);
extendedReaders.add(userb);
extendedWriters.add(usera);
extendedWriters.add(userb);
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
// Create a site
NodeRef documentLib = createSite(new HashSet<>(), new HashSet<>());
// Create records in the site document library
return createRecords(concurrentThreads, documentLib, owner);
}
private NodeRef createSite(Set<String> readers, Set<String> writers)
{
return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<NodeRef>() {
@Override
public NodeRef execute() throws Throwable
{
final String siteShortName = GUID.generate();
siteService.createSite(null, siteShortName, "test", "test", SiteVisibility.PRIVATE);
readers.forEach(reader -> siteService.setMembership(siteShortName, reader, SiteModel.SITE_CONSUMER));
writers.forEach(writer -> siteService.setMembership(siteShortName, writer, SiteModel.SITE_COLLABORATOR));
return siteService.createContainer(siteShortName, SiteService.DOCUMENT_LIBRARY, null, null);
}
}, false, true);
}
private Set<NodeRef> createRecords(int numRecords, NodeRef parent, String owner)
{
return retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Set<NodeRef>>() {
@Override
public Set<NodeRef> execute() throws Throwable
{
int createdRecords = 0;
Set<NodeRef> documents = new HashSet<>();
while (createdRecords < numRecords)
{
final NodeRef doc = fileFolderService.create(parent, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef();
ownableService.setOwner(doc, owner);
recordService.createRecord(filePlan, doc, rmFolder, true);
recordService.file(doc);
recordService.complete(doc);
documents.add(doc);
createdRecords++;
}
return documents;
}
}, false, true);
}
private void setExtendedSecurity(NodeRef doc, Set<String> readers, Set<String> writers, boolean useRetry)
{
if (!useRetry)
{
setExtendedSecurity(doc, readers, writers);
return;
}
retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Void>() {
@Override
public Void execute() throws Throwable
{
setExtendedSecurity(doc, readers, writers);
return null;
}
}, false, true);
}
private void setExtendedSecurity(NodeRef doc, Set<String> readers, Set<String> writers)
{
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
extendedSecurityService.set(doc, readers, writers);
}
private void fireParallelExecutionOfSetExtendedSecurity(Set<NodeRef> documents, Set<String> extendedReaders, Set<String> extendedWriters, boolean useRetry)
{
CompletableFuture<?>[] futures = documents.stream()
.map(doc -> CompletableFuture.runAsync(() -> setExtendedSecurity(doc, extendedReaders, extendedWriters, useRetry)))
.toArray(CompletableFuture[]::new);
try
{
CompletableFuture.allOf(futures).join();
}
catch (Exception e)
{
Throwable cause = e.getCause();
if (cause instanceof ConcurrencyFailureException)
{
throw (ConcurrencyFailureException) cause;
}
throw new RuntimeException("Error during parallel execution", e);
}
}
private void verifyCreatedGroups(Set<NodeRef> documents, boolean onlyDuplicatesValidation)
{
retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Void>() {
@Override
public Void execute() throws Throwable
{
Set<String> expectedAuthorities = null;
Set<Set<String>> errors = new HashSet<>();
for (NodeRef doc : documents)
{
Set<AccessPermission> permissions = permissionService.getAllSetPermissions(doc);
Set<String> authorities = getDocumentAuthorities(permissions);
Set<String> authoritiesById = getAuthorityIds(authorities);
verifyIPRGroups(authorities, onlyDuplicatesValidation);
if (onlyDuplicatesValidation)
{
// Some documents may not have IPR groups created if there was a ConcurrencyFailureException
continue;
}
// All documents should have the same exact set of groups assigned
if (expectedAuthorities == null)
{
expectedAuthorities = authoritiesById;
}
if (!expectedAuthorities.equals(authoritiesById))
{
errors.add(authoritiesById);
}
}
assertTrue("Unexpected authorities linked to document", errors.isEmpty());
return null;
}
}, false, true);
}
private Set<String> getDocumentAuthorities(Set<AccessPermission> permissions)
{
Set<String> authorities = new HashSet<>();
for (AccessPermission accessPermission : permissions)
{
String authority = accessPermission.getAuthority();
String authName = authorityService.getName(AuthorityType.GROUP, authority);
authorities.add(authName);
}
return authorities;
}
private Set<String> getAuthorityIds(Set<String> authorities)
{
Set<String> authorityIds = new HashSet<>();
for (String authority : authorities)
{
String authId = authorityService.getAuthorityNodeRef(authority) != null
? authorityService.getAuthorityNodeRef(authority).getId()
: null;
authorityIds.add(authId);
}
return authorityIds;
}
private void verifyIPRGroups(Set<String> authorities, boolean onlyDuplicatesValidation)
{
boolean hasGroupIPR = false;
for (String authorityName : authorities)
{
String shortName = authorityService.getShortName(authorityName);
if (authorityName.startsWith("GROUP_IPR"))
{
hasGroupIPR = true;
PagingResults<String> results = authorityService.getAuthorities(AuthorityType.GROUP, null, shortName, false,
false, new PagingRequest(0, 10));
assertEquals("No duplicated IPR group expected", 1, results.getPage().size());
}
}
if (!onlyDuplicatesValidation)
{
assertTrue("No IPR Groups created", hasGroupIPR);
}
}
}

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>25.3.0.25-SNAPSHOT</version>
<version>25.3.0.40</version>
</parent>
<properties>
@@ -51,8 +51,8 @@
</exclusions>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<scope>provided</scope>
</dependency>

View File

@@ -28,7 +28,7 @@ import java.util.ResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.json.simple.JSONObject;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.Status;
@@ -92,7 +92,7 @@ public class WikiPageGet extends AbstractWikiWebScript
{
links.add(link);
// build the list of available pages
WikiPageInfo wikiPage = wikiService.getWikiPage(site.getShortName(), StringEscapeUtils.unescapeHtml(link));
WikiPageInfo wikiPage = wikiService.getWikiPage(site.getShortName(), StringEscapeUtils.unescapeHtml4(link));
if (wikiPage != null)
{
pageTitles.add(wikiPage.getTitle());

View File

@@ -91,6 +91,15 @@ function doclist_getAllNodes(parsedArgs, filterParams, query, totalItemCount)
};
}
function sanitizeJunkFavouriteKeys(favourites){
for (var key in favourites) {
if (!key || key.trim() === "") {
delete favourites[key];
}
}
return favourites;
}
/**
* Main entry point: Create collection of documents and folders in the given space
*
@@ -123,6 +132,47 @@ function doclist_main()
if (logger.isLoggingEnabled())
logger.log("doclist.lib.js - NodeRef: " + parsedArgs.nodeRef + " Query: " + query);
favourites = sanitizeJunkFavouriteKeys(favourites);
if(query === null)
{
return {
luceneQuery: "",
paging: {
totalRecords: 0,
startIndex: 0
},
container: parsedArgs.rootNode,
parent: null,
onlineEditing: utils.moduleInstalled("org.alfresco.module.vti"),
itemCount: {
folders: 0,
documents: 0
},
items: [],
customJSON: slingshotDocLib.getJSON()
};
}
if(Object.keys(favourites).length === 0 && query === null)
{
return {
luceneQuery: "",
paging: {
totalRecords: 0,
startIndex: 0
},
container: parsedArgs.rootNode,
parent: null,
onlineEditing: utils.moduleInstalled("org.alfresco.module.vti"),
itemCount: {
folders: 0,
documents: 0
},
items: [],
customJSON: slingshotDocLib.getJSON()
};
}
var totalItemCount = filterParams.limitResults ? parseInt(filterParams.limitResults, 10) : -1;
// For all sites documentLibrary query we pull in all available results and post filter

View File

@@ -182,11 +182,14 @@ var Filters =
case "favourites":
for (var favourite in favourites)
{
if (filterQuery)
if (favourite && favourite.trim() !== "")
{
filterQuery += " OR ";
if (filterQuery)
{
filterQuery += " OR ";
}
filterQuery += "ID:\"" + favourite + "\"";
}
filterQuery += "ID:\"" + favourite + "\"";
}
if (filterQuery.length !== 0)
@@ -201,7 +204,13 @@ var Filters =
else
{
// empty favourites query
filterQuery = "+ID:\"\"";
logger.warn("No favourites found for user: " + person.properties.userName);
return {
query: null,
limitResults: 0,
sort: [],
language: "lucene"
};
}
filterParams.query = filterQuery;
@@ -224,15 +233,15 @@ var Filters =
filterParams.query = "+ID:\"" + parsedArgs.nodeRef + "\"";
break;
case "tag":
// Remove any trailing "/" character
if (filterData.charAt(filterData.length - 1) == "/")
{
filterData = filterData.slice(0, -1);
}
filterQuery = this.constructPathQuery(parsedArgs);
filterParams.query = filterQuery + " +PATH:\"/cm:taggable/cm:" + search.ISO9075Encode(filterData) + "/member\"";
break;
case "tag":
// Remove any trailing "/" character
if (filterData.charAt(filterData.length - 1) == "/")
{
filterData = filterData.slice(0, -1);
}
filterQuery = this.constructPathQuery(parsedArgs);
filterParams.query = filterQuery + " +TAG:\"" + search.ISO9075Encode(filterData) + "\"";
break;
case "category":
// Remove any trailing "/" character
@@ -240,8 +249,15 @@ var Filters =
{
filterData = filterData.slice(0, -1);
}
filterQuery = this.constructPathQuery(parsedArgs);
filterParams.query = filterQuery + " +PATH:\"/cm:categoryRoot/cm:generalclassifiable" + Filters.iso9075EncodePath(filterData) + "/member\"";
var categoryNodeRef = this.getCategoryNodeRef(filterData);
if (categoryNodeRef && search.findNode(categoryNodeRef) != null) {
filterParams.query = filterQuery + ' +@cm\\:categories:"' + categoryNodeRef + '"';
} else {
logger.warn("category filter: skipping invalid category node : " + categoryNodeRef);
}
filterParams.language = "fts-alfresco";
break;
case "aspect":
@@ -262,11 +278,24 @@ var Filters =
{
filterParams.query += " " + (Filters.TYPE_MAP[parsedArgs.type] || "");
}
logger.warn("Final Query : " + filterParams.query);
return filterParams;
},
constructPathQuery: function constructPathQuery(parsedArgs)
getCategoryNodeRef: function(categoryName) {
var results = search.luceneSearch(
'PATH:"/cm:categoryRoot/cm:generalclassifiable//*" AND @cm\\:name:"' + categoryName + '"'
);
if (results && results.length > 0) {
return results[0].nodeRef.toString();
}
logger.warn("Category not found: " + categoryName);
return null;
},
constructPathQuery: function(parsedArgs)
{
var pathQuery = "";
if (parsedArgs.libraryRoot != companyhome || parsedArgs.nodeRef != "alfresco://company/home")

View File

@@ -25,7 +25,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jakarta.transaction.UserTransaction;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONArray;
@@ -958,7 +958,7 @@ public class WikiRestApiTest extends BaseWebScriptTest
String link = m.group(1);
link += "?title=<script>alert('xss');</script>";
WikiPageInfo wikiPage2 = this.wikiService.getWikiPage(SITE_SHORT_NAME_WIKI, link);
WikiPageInfo wikiPage1 = this.wikiService.getWikiPage(SITE_SHORT_NAME_WIKI, StringEscapeUtils.unescapeHtml(link));
WikiPageInfo wikiPage1 = this.wikiService.getWikiPage(SITE_SHORT_NAME_WIKI, StringEscapeUtils.unescapeHtml4(link));
assertEquals(wikiPage2, wikiPage1);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,8 +37,7 @@ commons-fileupload http://jakarta.apache.org/commons/
commons-httpclient http://jakarta.apache.org/commons/
commons-io http://jakarta.apache.org/commons/
commons-jxpath http://jakarta.apache.org/commons/
commons-lang http://jakarta.apache.org/commons/
commons-lang3 http://jakarta.apache.org/commons/
commons-lang3 http://jakarta.apache.org/commons/
commons-logging http://jakarta.apache.org/commons/
commons-net http://jakarta.apache.org/commons/
commons-pool http://jakarta.apache.org/commons/

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ import java.util.Date;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisUnauthorizedException;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

View File

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

View File

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

View File

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

View File

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

View File

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

16
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.3.0.25-SNAPSHOT</version>
<version>25.3.0.40</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -51,10 +51,10 @@
<dependency.alfresco-server-root.version>7.0.2</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.2.1-A.3</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>4.2.1-A.4</dependency.alfresco-transform-service.version>
<dependency.alfresco-transform-core.version>5.2.2-A.1</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>4.2.2-A.1</dependency.alfresco-transform-service.version>
<dependency.alfresco-greenmail.version>7.1</dependency.alfresco-greenmail.version>
<dependency.acs-event-model.version>1.0.5</dependency.acs-event-model.version>
<dependency.acs-event-model.version>1.0.9</dependency.acs-event-model.version>
<dependency.aspectj.version>1.9.22.1</dependency.aspectj.version>
<dependency.spring.version>6.2.8</dependency.spring.version>
@@ -154,7 +154,7 @@
<connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection>
<developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection>
<url>https://github.com/Alfresco/alfresco-community-repo</url>
<tag>HEAD</tag>
<tag>25.3.0.40</tag>
</scm>
<distributionManagement>
@@ -422,9 +422,9 @@
<version>1.18.0</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.18.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>

View File

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

View File

@@ -40,6 +40,7 @@
"items":
[
<#list results as row>
<#if row.item.hasPermission("Read")>
{
"type": "${row.item.typeShort}",
"parentType": "${row.item.parentTypeShort!""}",
@@ -75,6 +76,7 @@
"nodeRef": "${row.item.nodeRef}"<#if row.selectable?exists>,
"selectable" : ${row.selectable?string}</#if>
}<#if row_has_next>,</#if>
</#if>
</#list>
]
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.3.0.25-SNAPSHOT</version>
<version>25.3.0.40</version>
</parent>
<dependencies>
@@ -94,7 +94,6 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.18.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
@@ -737,10 +736,6 @@
<artifactId>reflections</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -70,6 +70,13 @@ public interface AuditComponent
*/
public void setUserAuditFilter(UserAuditFilter userAuditFilter);
/**
* @param auditRecordReporter
* AuditRecordReporter
* @since 25.3
*/
public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter);
/**
* Get all registered audit applications, whether active or not.
*

View File

@@ -48,7 +48,6 @@ import org.alfresco.repo.audit.model.AuditModelRegistryImpl;
import org.alfresco.repo.domain.audit.AuditDAO;
import org.alfresco.repo.domain.propval.PropertyValueDAO;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -73,8 +72,8 @@ public class AuditComponentImpl implements AuditComponent
{
private static final String INBOUND_LOGGER = "org.alfresco.repo.audit.inbound";
private static Log logger = LogFactory.getLog(AuditComponentImpl.class);
private static Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER);
private static final Log logger = LogFactory.getLog(AuditComponentImpl.class);
private static final Log loggerInbound = LogFactory.getLog(INBOUND_LOGGER);
private AuditModelRegistryImpl auditModelRegistry;
private PropertyValueDAO propertyValueDAO;
@@ -82,6 +81,7 @@ public class AuditComponentImpl implements AuditComponent
private TransactionService transactionService;
private AuditFilter auditFilter;
private UserAuditFilter userAuditFilter;
private AuditRecordReporter auditRecordReporter;
/**
* Default constructor
@@ -140,6 +140,11 @@ public class AuditComponentImpl implements AuditComponent
this.userAuditFilter = userAuditFilter;
}
public void setAuditRecordReporter(AuditRecordReporter auditRecordReporter)
{
this.auditRecordReporter = auditRecordReporter;
}
/**
* {@inheritDoc}
*
@@ -215,7 +220,7 @@ public class AuditComponentImpl implements AuditComponent
public int deleteAuditEntries(List<Long> auditEntryIds)
{
// Shortcut, if necessary
if (auditEntryIds.size() == 0)
if (auditEntryIds.isEmpty())
{
return 0;
}
@@ -234,7 +239,7 @@ public class AuditComponentImpl implements AuditComponent
{
Long disabledPathsId = application.getDisabledPathsId();
Set<String> disabledPaths = (Set<String>) propertyValueDAO.getPropertyById(disabledPathsId);
return new HashSet<String>(disabledPaths);
return new HashSet<>(disabledPaths);
}
catch (Throwable e)
{
@@ -254,6 +259,16 @@ public class AuditComponentImpl implements AuditComponent
return auditModelRegistry.isAuditEnabled();
}
public boolean isAuditingToDatabaseEnabled()
{
return auditModelRegistry.isAuditingToDatabaseEnabled();
}
public boolean isAuditingToAuditStorageEnabled()
{
return auditModelRegistry.isAuditingToAuditStorageEnabled();
}
/**
* {@inheritDoc}
*
@@ -309,7 +324,7 @@ public class AuditComponentImpl implements AuditComponent
{
PathMapper pathMapper = auditModelRegistry.getAuditPathMapper();
Set<String> mappedPaths = pathMapper.getMappedPathsWithPartialMatch(path);
return loggerInbound.isDebugEnabled() || mappedPaths.size() > 0;
return loggerInbound.isDebugEnabled() || !mappedPaths.isEmpty();
}
/**
@@ -346,7 +361,7 @@ public class AuditComponentImpl implements AuditComponent
// Check if there are any entries that match or supercede the given path
String disablingPath = null;
;
for (String disabledPath : disabledPaths)
{
if (path.startsWith(disabledPath))
@@ -573,7 +588,7 @@ public class AuditComponentImpl implements AuditComponent
}
// Build the key paths using the session root path
Map<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2);
Map<String, Serializable> pathedValues = new HashMap<>(values.size() * 2);
for (Map.Entry<String, Serializable> entry : values.entrySet())
{
String pathElement = entry.getKey();
@@ -596,12 +611,7 @@ public class AuditComponentImpl implements AuditComponent
case TXN_NONE:
case TXN_READ_ONLY:
// New transaction
RetryingTransactionCallback<Map<String, Serializable>> callback = new RetryingTransactionCallback<Map<String, Serializable>>() {
public Map<String, Serializable> execute() throws Throwable
{
return recordAuditValuesImpl(mappedValues);
}
};
RetryingTransactionCallback<Map<String, Serializable>> callback = () -> recordAuditValuesImpl(mappedValues);
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
txnHelper.setForceWritable(true);
return txnHelper.doInTransaction(callback, false, true);
@@ -618,21 +628,16 @@ public class AuditComponentImpl implements AuditComponent
public Map<String, Serializable> recordAuditValuesImpl(Map<String, Serializable> mappedValues)
{
// Group the values by root path
Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<String, Map<String, Serializable>>();
Map<String, Map<String, Serializable>> mappedValuesByRootKey = new HashMap<>();
for (Map.Entry<String, Serializable> entry : mappedValues.entrySet())
{
String path = entry.getKey();
String rootKey = AuditApplication.getRootKey(path);
Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.get(rootKey);
if (rootKeyMappedValues == null)
{
rootKeyMappedValues = new HashMap<String, Serializable>(7);
mappedValuesByRootKey.put(rootKey, rootKeyMappedValues);
}
Map<String, Serializable> rootKeyMappedValues = mappedValuesByRootKey.computeIfAbsent(rootKey, k -> new HashMap<>(7));
rootKeyMappedValues.put(path, entry.getValue());
}
Map<String, Serializable> allAuditedValues = new HashMap<String, Serializable>(mappedValues.size() * 2 + 1);
Map<String, Serializable> allAuditedValues = new HashMap<>(mappedValues.size() * 2 + 1);
// Now audit for each of the root keys
for (Map.Entry<String, Map<String, Serializable>> entry : mappedValuesByRootKey.entrySet())
{
@@ -694,7 +699,7 @@ public class AuditComponentImpl implements AuditComponent
}
// Check if there is anything to audit
if (values.size() == 0)
if (values.isEmpty())
{
if (logger.isDebugEnabled())
{
@@ -727,12 +732,7 @@ public class AuditComponentImpl implements AuditComponent
Map<String, Serializable> auditData = generateData(generators);
// Now extract values
Map<String, Serializable> extractedData = AuthenticationUtil.runAs(new RunAsWork<Map<String, Serializable>>() {
public Map<String, Serializable> doWork() throws Exception
{
return extractData(application, values);
}
}, AuthenticationUtil.getSystemUserName());
Map<String, Serializable> extractedData = AuthenticationUtil.runAs(() -> extractData(application, values), AuthenticationUtil.getSystemUserName());
// Combine extracted and generated values (extracted data takes precedence)
auditData.putAll(extractedData);
@@ -743,8 +743,8 @@ public class AuditComponentImpl implements AuditComponent
{
String root = value.getKey();
int index = root.lastIndexOf("/");
Map<String, Serializable> argc = new HashMap<String, Serializable>(1);
argc.put(root.substring(index, root.length()).substring(1), value.getValue());
Map<String, Serializable> argc = new HashMap<>(1);
argc.put(root.substring(index).substring(1), value.getValue());
if (!auditFilter.accept(root.substring(0, index), argc))
{
return Collections.emptyMap();
@@ -760,10 +760,15 @@ public class AuditComponentImpl implements AuditComponent
{
// Persist the values (if not just gathering data in a pre call for use in a post call)
boolean justGatherPreCallData = application.isApplicationJustGeneratingPreCallData();
if (!justGatherPreCallData)
if (!justGatherPreCallData && isAuditingToDatabaseEnabled())
{
entryId = auditDAO.createAuditEntry(applicationId, time, username, auditData);
}
if (isAuditingToAuditStorageEnabled())
{
auditRecordReporter.reportAuditRecord(createAuditRecord(auditData, true, username, entryId, application.getApplicationName()));
}
// Done
if (logger.isDebugEnabled())
{
@@ -822,7 +827,7 @@ public class AuditComponentImpl implements AuditComponent
AuditApplication application,
Map<String, Serializable> values)
{
Map<String, Serializable> newData = new HashMap<String, Serializable>(values.size());
Map<String, Serializable> newData = new HashMap<>(values.size());
List<DataExtractorDefinition> extractors = application.getDataExtractors();
for (DataExtractorDefinition extractorDef : extractors)
@@ -900,7 +905,7 @@ public class AuditComponentImpl implements AuditComponent
*/
private Map<String, Serializable> generateData(Map<String, DataGenerator> generators)
{
Map<String, Serializable> newData = new HashMap<String, Serializable>(generators.size() + 5);
Map<String, Serializable> newData = new HashMap<>(generators.size() + 5);
for (Map.Entry<String, DataGenerator> entry : generators.entrySet())
{
String path = entry.getKey();
@@ -925,6 +930,20 @@ public class AuditComponentImpl implements AuditComponent
return newData;
}
/**
* Creates an AuditRecord from the provided audit data.
*/
private AuditRecord createAuditRecord(Map<String, Serializable> auditData, boolean inTransaction, String username, Long entryId, String applicationName)
{
int rootSize = applicationName.length() + 2; // Root is constructed like this -> '/' + auditedApplicationName + '/'.
AuditRecord.Builder builder = AuditRecordUtils.generateAuditRecordBuilder(auditData, rootSize);
builder.setAuditRecordType(applicationName);
builder.setInTransaction(inTransaction);
builder.setUsername(username);
builder.setEntryDBId(entryId);
return builder.build();
}
/**
* {@inheritDoc}
*/

View File

@@ -0,0 +1,130 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 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.audit;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.Map;
public class AuditRecord
{
private final boolean inTransaction;
private final String auditApplicationId;
private final ZonedDateTime createdAt;
private final String username;
private final Long entryDBId;
private final Map<String, Serializable> auditData;
public AuditRecord(Builder builder)
{
this.auditApplicationId = builder.auditRecordType;
this.inTransaction = builder.inTransaction;
this.auditData = builder.auditRecordData;
this.createdAt = ZonedDateTime.now();
this.username = builder.username;
this.entryDBId = builder.entryDBId;
}
public String getAuditApplicationId()
{
return auditApplicationId;
}
public boolean isInTransaction()
{
return inTransaction;
}
public ZonedDateTime getCreatedAt()
{
return createdAt;
}
public String getUsername()
{
return username;
}
public Long getEntryDBId()
{
return entryDBId;
}
public Map<String, Serializable> getAuditData()
{
return auditData;
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private String auditRecordType;
private boolean inTransaction;
private Map<String, Serializable> auditRecordData;
private String username;
private Long entryDBId;
public Builder setAuditRecordType(String auditRecordType)
{
this.auditRecordType = auditRecordType;
return this;
}
public Builder setInTransaction(boolean inTransaction)
{
this.inTransaction = inTransaction;
return this;
}
public Builder setAuditRecordData(Map<String, Serializable> auditRecordData)
{
this.auditRecordData = auditRecordData;
return this;
}
public Builder setUsername(String username)
{
this.username = username;
return this;
}
public Builder setEntryDBId(Long entryDBId)
{
this.entryDBId = entryDBId;
return this;
}
public AuditRecord build()
{
return new AuditRecord(this);
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 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.audit;
public interface AuditRecordReporter
{
/**
* This method will report AuditRecord to Audit Storage using RepoEvent2
*
* @param auditRecord
* represent data that will be reported.
*/
void reportAuditRecord(AuditRecord auditRecord);
}

View File

@@ -0,0 +1,40 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 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.audit;
public class AuditRecordReporterImpl implements AuditRecordReporter
{
/**
* This method intentionally has an empty implementation.
* <p>
* This class provides a no-op implementation of {@link AuditRecordReporter}.
*/
@Override
public void reportAuditRecord(AuditRecord auditRecord)
{
// No operation performed.
}
}

View File

@@ -0,0 +1,104 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 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.audit;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.service.cmr.repository.NodeRef;
public final class AuditRecordUtils
{
private AuditRecordUtils()
{
// This is a utility class and cannot be instantiated.
}
/**
* Generates an {@link AuditRecord.Builder} from flat audit data.
* <p>
* This method:
* <ul>
* <li>Translates flat {@code key-value} pairs into a nested JSON structure.</li>
* <li>Preloads the builder with the provided arguments.</li>
* <li>Splits keys by {@code /} to build the nested structure.</li>
* <li>Uses the root key as the application ID.</li>
* <li>Assumes each key starts with the same root, constructed as {@code '/' + auditedApplicationName + '/'}, which is removed before splitting.</li>
* </ul>
*
* @param data
* a map containing flat audit data as `key-value` pairs
* @param keyRootLength
* is a length of key root.
* @return a preloaded {@link AuditRecord.Builder}
*/
public static AuditRecord.Builder generateAuditRecordBuilder(Map<String, Serializable> data, int keyRootLength)
{
var auditRecordBuilder = AuditRecord.builder();
var rootNode = createRootNode(data, keyRootLength);
auditRecordBuilder.setAuditRecordData(rootNode);
return auditRecordBuilder;
}
@SuppressWarnings("unchecked")
private static HashMap<String, Serializable> createRootNode(Map<String, Serializable> data, int keyRootLength)
{
var rootNode = new HashMap<String, Serializable>();
data.forEach((k, v) -> {
var keys = k.substring(keyRootLength).split("/");
var current = rootNode;
for (int i = 0; i < keys.length - 1; i++)
{
current = (HashMap<String, Serializable>) current.computeIfAbsent(keys[i], newMap -> new HashMap<String, Serializable>());
}
current.put(keys[keys.length - 1], decodeValueByInstance(v));
});
return rootNode;
}
@SuppressWarnings("unchecked")
private static Serializable decodeValueByInstance(Serializable value)
{
if (value instanceof HashMap<?, ?>)
{
return createRootNode((HashMap<String, Serializable>) value, 0);
}
else if (value instanceof NodeRef)
{
return ((NodeRef) value).getId();
}
else
{
return value;
}
}
}

View File

@@ -58,6 +58,21 @@ public interface AuditModelRegistry
*/
public boolean isAuditEnabled();
/**
* Determines whether audit values should be stored in database. <code>True</code> by default if not changed by property.
*
* @return <code>true</code> if audit is enabled.
*/
boolean isAuditingToDatabaseEnabled();
/**
* Determines whether audit values should be stored in audit storage.
*
* @return <code>true</code> if auditing to Audit Storage is enabled.
*
*/
boolean isAuditingToAuditStorageEnabled();
/**
* Get a map of all audit applications key by name
*

View File

@@ -85,6 +85,9 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement
{
/** The name of the global enablement property. */
public static final String PROPERTY_AUDIT_ENABLED = "audit.enabled";
private static final String AUDITING_TO_DATABASE = ".auditingToDatabase";
private static final String AUDITING_TO_AUDIT_STORAGE = ".auditingToAuditStorage";
/** The name of the strict loading flag. */
public static final String PROPERTY_AUDIT_CONFIG_STRICT = "audit.config.strict";
/** The XSD classpath location. */
@@ -249,6 +252,26 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement
return value != null && value.equalsIgnoreCase("true");
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAuditingToDatabaseEnabled()
{
String value = getProperty(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_DATABASE);
return value == null || value.equalsIgnoreCase("true");
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAuditingToAuditStorageEnabled()
{
String value = getProperty(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_AUDIT_STORAGE);
return value != null && value.equalsIgnoreCase("true");
}
/**
* Enables audit and registers an audit model at a given URL. Does not register across the cluster and should only be used for unit test purposes.
*
@@ -296,6 +319,8 @@ public class AuditModelRegistryImpl extends AbstractPropertyBackedBean implement
// Default value for global enabled property
properties.put(AUDIT_PROPERTY_AUDIT_ENABLED, false);
properties.put(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_DATABASE, true);
properties.put(AUDIT_PROPERTY_AUDIT_ENABLED + AUDITING_TO_AUDIT_STORAGE, false);
// Let's search for config files in the appropriate places. The individual applications they contain can still
// be enabled/disabled by the bean properties

View File

@@ -116,7 +116,7 @@ public class NodeDAOImpl extends AbstractNodeDAOImpl
private static final String SELECT_NODE_MAX_ID = "alfresco.node.select_NodeMaxId";
private static final String SELECT_NODE_INTERVAL_BY_TYPE = "alfresco.node.select_MinMaxNodeIdForNodeType";
private static final String SELECT_NODES_WITH_ASPECT_IDS = "alfresco.node.select_NodesWithAspectIds";
private static final String SELECT_NODES_WITH_ASPECT_IDS_LIMITED = "alfresco.node.select_NodesWithAspectIds_Limited";
private static final String SELECT_NODES_WITH_ASPECT_IDS_LIMITED = "alfresco.node.select.select_NodesWithAspectIds_Limited";
private static final String INSERT_NODE_ASSOC = "alfresco.node.insert.insert_NodeAssoc";
private static final String UPDATE_NODE_ASSOC = "alfresco.node.update_NodeAssoc";
private static final String DELETE_NODE_ASSOC = "alfresco.node.delete_NodeAssoc";

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2020 Alfresco Software Limited
* 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
@@ -38,7 +38,7 @@ import org.alfresco.util.Pair;
*/
public enum EventJSONSchema
{
NODE_CREATED_V1("nodeCreated", 1, EventType.NODE_CREATED), NODE_UPDATED_V1("nodeUpdated", 1, EventType.NODE_UPDATED), NODE_DELETED_V1("nodeDeleted", 1, EventType.NODE_DELETED), CHILD_ASSOC_CREATED_V1("childAssocCreated", 1, EventType.CHILD_ASSOC_CREATED), CHILD_ASSOC_DELETED_V1("childAssocDeleted", 1, EventType.CHILD_ASSOC_DELETED), PEER_ASSOC_CREATED_V1("peerAssocCreated", 1, EventType.PEER_ASSOC_CREATED), PEER_ASSOC_DELETED_V1("peerAssocDeleted", 1, EventType.PEER_ASSOC_DELETED), PERMISSION_UPDATED_V1("permissionUpdated", 1, EventType.PERMISSION_UPDATED);
NODE_CREATED_V1("nodeCreated", 1, EventType.NODE_CREATED), NODE_UPDATED_V1("nodeUpdated", 1, EventType.NODE_UPDATED), NODE_DELETED_V1("nodeDeleted", 1, EventType.NODE_DELETED), CHILD_ASSOC_CREATED_V1("childAssocCreated", 1, EventType.CHILD_ASSOC_CREATED), CHILD_ASSOC_DELETED_V1("childAssocDeleted", 1, EventType.CHILD_ASSOC_DELETED), PEER_ASSOC_CREATED_V1("peerAssocCreated", 1, EventType.PEER_ASSOC_CREATED), PEER_ASSOC_DELETED_V1("peerAssocDeleted", 1, EventType.PEER_ASSOC_DELETED), PERMISSION_UPDATED_V1("permissionUpdated", 1, EventType.PERMISSION_UPDATED), AUDIT_ENTRY_CREATED_V1("auditEntryCreated", 1, EventType.AUDIT_ENTRY_CREATED);
private static final String PREFIX = "https://api.alfresco.com/schema/event/repo/v";

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* 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
@@ -124,6 +124,9 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
/** Number of (bytecode) instructions that will trigger the observer */
private int observerInstructionCount = 100;
/** Flag to enable or disable scope cleaning at the end of each script execution */
private boolean cleanScope = true;
/** Custom context factory */
public static AlfrescoContextFactory contextFactory;
@@ -210,6 +213,15 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
this.observerInstructionCount = observerInstructionCount;
}
/**
* @param cleanScope
* true to enable scope cleaning at the end of each script execution - set to false to disable this feature.
*/
public void setCleanScope(boolean cleanScope)
{
this.cleanScope = cleanScope;
}
/**
* @see org.alfresco.service.cmr.repository.ScriptProcessor#reset()
*/
@@ -619,7 +631,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
}
finally
{
if (!secure)
if (!secure && cleanScope)
{
unsetScope(model, scope);
}

View File

@@ -81,11 +81,19 @@ 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.
@@ -288,7 +296,7 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
{
try
{
if (!isEnabled())
if (!isAsyncAllowed(renderOrTransform))
{
throw new RenditionService2Exception("Async transforms and renditions are disabled " +
"(system.thumbnail.generate=false or renditionService2.enabled=false).");
@@ -967,4 +975,24 @@ 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

@@ -74,7 +74,7 @@ import com.nimbusds.oauth2.sdk.id.Identifier;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hc.client5.http.classic.HttpClient;

View File

@@ -42,7 +42,7 @@ 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.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;

View File

@@ -37,8 +37,9 @@
<property name="properties" ref="global-properties" />
</bean>
</property>
<property name="auditRecordReporter" ref="auditRecordReporter"/>
</bean>
<!-- User Audit Filter -->
<bean id="userAuditFilter" class="org.alfresco.repo.audit.UserAuditFilter">
@@ -108,5 +109,8 @@
<!-- Reference in the audit registry managed bean -->
<alias name="Audit" alias="auditModel.modelRegistry"/>
</beans>
<!-- Audit Record Reported -->
<bean id="auditRecordReporter" class="org.alfresco.repo.audit.AuditRecordReporterImpl"/>
</beans>

View File

@@ -782,25 +782,6 @@
<if test="ordered == true">order by node.id ASC</if>
</select>
<select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="result_NodeRef" >
select
node.id as id,
store.protocol as protocol,
store.identifier as identifier,
node.uuid as uuid
from
alf_node_aspects na
join alf_node node on (na.node_id = node.id)
left join alf_store store on (store.id = node.store_id)
where
<![CDATA[na.node_id >= #{idOne}]]>
<if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if>
and na.qname_id in
<foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach>
<if test="ordered == true">order by node.id ASC</if>
<if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if>
</select>
<!-- Common results for result_NodeAssoc -->
<sql id="select_NodeAssoc_Results">
select
@@ -1566,4 +1547,4 @@
</foreach>
</delete>
</mapper>
</mapper>

View File

@@ -30,4 +30,23 @@
<![CDATA[and commit_time_ms <= #{maxCommitTime}]]>
</select>
</mapper>
<select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="alfresco.node.result_NodeRef" >
select
node.id as id,
store.protocol as protocol,
store.identifier as identifier,
node.uuid as uuid
from
alf_node_aspects na
join alf_node node on (na.node_id = node.id)
left join alf_store store on (store.id = node.store_id)
where
<![CDATA[na.node_id >= #{idOne}]]>
<if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if>
and na.qname_id in
<foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach>
<if test="ordered == true">order by node.id ASC</if>
<if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if>
</select>
</mapper>

View File

@@ -30,4 +30,23 @@
<![CDATA[and commit_time_ms <= #{maxCommitTime}]]>
</select>
</mapper>
<select id="select_NodesWithAspectIds_Limited" parameterType="Ids" resultMap="alfresco.node.result_NodeRef" >
select
node.id as id,
store.protocol as protocol,
store.identifier as identifier,
node.uuid as uuid
from
alf_node_aspects na
join alf_node node on (na.node_id = node.id)
left join alf_store store on (store.id = node.store_id)
where
<![CDATA[na.node_id >= #{idOne}]]>
<if test="idTwo != null"><![CDATA[and na.node_id < #{idTwo}]]></if>
and na.qname_id in
<foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach>
<if test="ordered == true">order by node.id ASC</if>
<if test="maxResults != null"><![CDATA[limit #{maxResults}]]></if>
</select>
</mapper>

View File

@@ -286,6 +286,8 @@ audit.alfresco-access.enabled=false
audit.alfresco-access.sub-actions.enabled=false
audit.cmischangelog.enabled=false
audit.dod5015.enabled=false
audit.enabled.auditingToAuditStorage=false
audit.enabled.auditingToDatabase=true
# Setting this flag to true will force startup failure when invalid audit configurations are detected
audit.config.strict=false
# Audit map filter for AccessAuditor - restricts recorded events to user driven events
@@ -1394,6 +1396,9 @@ scripts.execution.maxMemoryUsedInBytes=-1
# Number of instructions that will trigger the observer
scripts.execution.observerInstructionCount=5000
# Flag to control if the scope is cleaned at the end of script execution
scripts.execution.clean.scope=true
# Default value being used in POST/size-details endpoint to partition a huge folder into smaller chunks
# so that we can compute more efficiently and consolidate all sizes into a single unit.
default.async.folder.items=1000

View File

@@ -60,6 +60,9 @@
<property name="observerInstructionCount">
<value>${scripts.execution.observerInstructionCount}</value>
</property>
<property name="cleanScope">
<value>${scripts.execution.clean.scope}</value>
</property>
</bean>
<!-- base config implementation that script extension beans extend from - for auto registration

View File

@@ -48,6 +48,7 @@ import org.alfresco.util.testing.category.NonBuildTests;
org.alfresco.repo.audit.UserAuditFilterTest.class,
org.alfresco.repo.audit.AuditMethodInterceptorTest.class,
org.alfresco.repo.audit.access.AccessAuditorTest.class,
org.alfresco.repo.audit.AuditRecordUtilsTest.class,
// the following test will lock up the DB if run in the applicationContext_01 test suite
org.alfresco.repo.activities.feed.FeedNotifierTest.class,

View File

@@ -877,6 +877,7 @@ public class AuditComponentTest extends TestCase
auditModelRegistry.loadAuditModels();
auditModelRegistry.setProperty("audit.enabled", "true");
auditModelRegistry.setProperty("audit.enabled.auditingToDatabase", "true");
auditModelRegistry.setProperty("audit.app1.enabled", "true");
auditModelRegistry.setProperty("audit.filter.app1.default.enabled", "true");

View File

@@ -0,0 +1,123 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 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.audit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.Serializable;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QName;
public class AuditRecordUtilsTest
{
@SuppressWarnings("unchecked")
@Test
public void testGenerateAuditRecordBuilderTest()
{
var testData = new HashMap<String, Serializable>();
testData.put("/alfresco-access/transaction/path", "/app:company_home");
testData.put("/alfresco-access/transaction/user", "admin");
testData.put("/alfresco-access/transaction/sub-actions", "updateNodeProperties");
var now = Instant.now();
testData.put("/alfresco-access/transaction/properties/from", (Serializable) Map.of(QName.createQName("modified"), Date.from(now)));
testData.put("/alfresco-access/transaction/properties/to", (Serializable) Map.of(QName.createQName("modified"), Date.from(now)));
var builder = AuditRecordUtils.generateAuditRecordBuilder(testData, "/alfresco-access/".length());
builder.setAuditRecordType("alfresco-access");
var auditRecord = builder.build();
assertNotNull(auditRecord);
assertEquals("alfresco-access", auditRecord.getAuditApplicationId());
var auditData = auditRecord.getAuditData();
assertEquals(1, auditData.size());
var transaction = (HashMap<String, ?>) auditData.get("transaction");
assertNotNull(transaction);
assertEquals(4, transaction.size());
assertEquals(testData.get("/alfresco-access/transaction/path"), transaction.get("path"));
assertEquals(testData.get("/alfresco-access/transaction/user"), transaction.get("user"));
assertEquals(testData.get("/alfresco-access/transaction/sub-actions"), transaction.get("sub-actions"));
var properties = (HashMap<String, Object>) transaction.get("properties");
assertNotNull(properties);
assertEquals(2, properties.size());
assertEquals(testData.get("/alfresco-access/transaction/properties/from"), properties.get("from"));
assertEquals(testData.get("/alfresco-access/transaction/properties/to"), properties.get("to"));
}
@SuppressWarnings("unchecked")
@Test
public void testGenerateAuditRecordBuilderTestNodeRef()
{
var testData = new HashMap<String, Serializable>();
var expectedValue = new HashMap<String, Serializable>();
expectedValue.put("nodeRef", new NodeRef("workspace://SpacesStore/bfa612e6-1a02-46a0-a612-e61a02e6a036"));
expectedValue.put("objectId", "bfa612e6-1a02-46a0-a612-e61a02e6a036;1.0");
testData.put("/CMISChangeLog/CREATED/result/value", expectedValue);
var builder = AuditRecordUtils.generateAuditRecordBuilder(testData, "/CMISChangeLog/".length());
builder.setAuditRecordType("CMISChangeLog");
var auditRecord = builder.build();
assertNotNull(auditRecord);
assertEquals("CMISChangeLog", auditRecord.getAuditApplicationId());
var auditData = auditRecord.getAuditData();
assertEquals(1, auditData.size());
var created = (HashMap<String, ?>) auditData.get("CREATED");
assertNotNull(created);
assertEquals(1, created.size());
var result = (HashMap<String, Object>) created.get("result");
assertNotNull(result);
assertEquals(1, result.size());
var resultValue = (HashMap<String, Object>) result.get("value");
assertNotNull(resultValue);
assertEquals(2, resultValue.size());
var expectedNodeRef = (NodeRef) expectedValue.get("nodeRef");
assertEquals(expectedNodeRef.getId(), resultValue.get("nodeRef"));
assertEquals(expectedValue.get("objectId"), resultValue.get("objectId"));
}
}

View File

@@ -50,6 +50,7 @@ public class AuditTestSuite extends TestSuite
suite.addTestSuite(UserAuditFilterTest.class);
suite.addTestSuite(AuditMethodInterceptorTest.class);
suite.addTest(new JUnit4TestAdapter(AuditRecordUtilsTest.class));
suite.addTest(new JUnit4TestAdapter(PropertyAuditFilterTest.class));
suite.addTest(new JUnit4TestAdapter(AccessAuditorTest.class));

View File

@@ -39,6 +39,7 @@ import org.junit.Test;
import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
@@ -776,4 +777,58 @@ public class RenditionService2IntegrationTest extends AbstractRenditionIntegrati
}
}
@Test
public void testTextExtractTransformAllowedWhenThumbnailDisabled()
{
// create a source node
NodeRef sourceNodeRef = createSource(ADMIN, "quick.pdf");
assertNotNull("Node not generated", sourceNodeRef);
String replyQueue = "org.test.queue";
String targetMimetype = MimetypeMap.MIMETYPE_TEXT_PLAIN;
TransformDefinition textExtractTransform = new TransformDefinition(
targetMimetype,
java.util.Collections.emptyMap(),
"clientData",
replyQueue,
"requestId");
renditionService2.setThumbnailsEnabled(false);
try
{
// Should NOT throw, as this is a text extract transform
AuthenticationUtil.runAs(() -> {
transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
renditionService2.transform(sourceNodeRef, textExtractTransform);
return null;
});
return null;
}, ADMIN);
}
finally
{
renditionService2.setThumbnailsEnabled(true);
}
}
@Test
public void testMetadataExtractTransformAllowedWhenThumbnailDisabled()
{
// create a source node
NodeRef sourceNodeRef = createSource(ADMIN, "quick.pdf");
assertNotNull("Node not generated", sourceNodeRef);
renditionService2.setThumbnailsEnabled(false);
try
{
// Should NOT throw, as this is a metadata extract transform
extract(ADMIN, sourceNodeRef);
waitForExtract(ADMIN, sourceNodeRef, true);
}
finally
{
renditionService2.setThumbnailsEnabled(true);
}
}
}