Compare commits

...

89 Commits

Author SHA1 Message Date
alfresco-build
b3be0f2b7f [maven-release-plugin][skip ci] prepare release 23.6.0.19 2025-09-18 11:12:59 +00:00
Belal Ansari
7a6ebb9a05 Backporting MNT-25216 Error on fixedAclUpdaterJobDetail execution when using Oracle to ACS 23 (#3575) 2025-09-18 15:57:15 +05:30
alfresco-build
fa0f239618 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-17 01:05:50 +00:00
alfresco-build
43799408a8 [maven-release-plugin][skip ci] prepare release 23.6.0.18 2025-09-17 01:05:48 +00:00
Tiago Salvado
e7305006f0 [ACS-9929] Bump cxf version to 4.1.2 (#3572) 2025-09-17 01:21:54 +01:00
alfresco-build
40c30411af [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-12 11:02:12 +00:00
alfresco-build
91f8b43237 [maven-release-plugin][skip ci] prepare release 23.6.0.17 2025-09-12 11:02:09 +00:00
Eva Vasques
6fccf828e1 ACS-10042 - AGS Concurrent IPR Group Creation (#3566) (#3570) 2025-09-12 11:10:43 +01:00
alfresco-build
3fac3373c9 [maven-release-plugin][skip ci] prepare for next development iteration 2025-09-04 08:24:19 +00:00
alfresco-build
ee857ce1de [maven-release-plugin][skip ci] prepare release 23.6.0.16 2025-09-04 08:24:17 +00:00
Debjit Chattopadhyay
483d7fab21 Merge pull request #3563 from Alfresco/fix/MNT-24776-backport-23.N
backport MNT-24776 to 23.N
2025-09-04 13:01:59 +05:30
Debjit Chattopadhyay
590209b299 Fix for "MNT-24776 : Category Picker Error when a User does not have Read Permissions to a Category"
(cherry picked from commit 626640ddc7)
2025-09-03 15:14:48 +05:30
alfresco-build
376514df67 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-29 13:30:01 +00:00
alfresco-build
7144a2dd94 [maven-release-plugin][skip ci] prepare release 23.6.0.15 2025-08-29 13:29:59 +00:00
Debjit Chattopadhyay
b4da3d8c20 MNT-24308 - fix search filterquery creation for displaying favourites in Share 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-29 18:15:59 +05:30
Debjit Chattopadhyay
62de9ff0c0 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-29 16:25:23 +05:30
alfresco-build
a11acce720 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-28 13:37:40 +00:00
alfresco-build
1128011e15 [maven-release-plugin][skip ci] prepare release 23.6.0.14 2025-08-28 13:37:38 +00:00
Tiago Salvado
d0cb45de0d [MNT-25242] Add property to control if scope is cleaned (#3520) (#3550) 2025-08-28 11:18:05 +01:00
alfresco-build
2b48195896 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-28 10:12:53 +00:00
alfresco-build
fbb95d6a7f [maven-release-plugin][skip ci] prepare release 23.6.0.13 2025-08-28 10:12:51 +00:00
cezary-witkowski
502427e852 [ACS-9933] Use only lang3 3.18 in 23.N (#3547)
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
2025-08-28 11:31:18 +02:00
alfresco-build
3ff2d79641 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-22 11:54:40 +00:00
alfresco-build
f274b88ece [maven-release-plugin][skip ci] prepare release 23.6.0.12 2025-08-22 11:54:38 +00:00
SatyamSah5
21550ec30b ACS-9991 Allow Content and Metadata extract even if thumbnails are disabled (#3537) 2025-08-22 16:39:46 +05:30
alfresco-build
8665267225 [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-07 20:47:40 +00:00
alfresco-build
984b0bc719 [maven-release-plugin][skip ci] prepare release 23.6.0.11 2025-08-07 20:47:38 +00:00
Eva Vasques
5b89fc0be7 MNT-24975 - Repeated IPR groups due to casing inconsistencies on creation (#3510) 2025-08-07 21:04:26 +01:00
alfresco-build
bf3a3382fd [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-07 13:51:45 +00:00
alfresco-build
14d007fae8 [maven-release-plugin][skip ci] prepare release 23.6.0.10 2025-08-07 13:51:43 +00:00
Eva Vasques
79317ddc9d ACS-9923 Removing an aspect needs to invoke onUpdateProperties (#3504) (#3506) 2025-08-07 13:51:29 +01:00
alfresco-build
c0e762fe5e [maven-release-plugin][skip ci] prepare for next development iteration 2025-08-06 06:04:44 +00:00
alfresco-build
5109b99520 [maven-release-plugin][skip ci] prepare release 23.6.0.9 2025-08-06 06:04:42 +00:00
Belal Ansari
dfc6306331 Feature/ACS 9933 vulnerability in commons lang3 (#3503) 2025-08-06 10:53:05 +05:30
alfresco-build
731f98921f [maven-release-plugin][skip ci] prepare for next development iteration 2025-07-30 13:35:32 +00:00
alfresco-build
0b21dbdc0a [maven-release-plugin][skip ci] prepare release 23.6.0.8 2025-07-30 13:35:29 +00:00
Eva Vasques
dd928356b8 MNT-24975 - Repeated IPR groups due to casing inconsistencies (#3459) (#3495) 2025-07-30 13:46:19 +01:00
alfresco-build
1844d8bdb9 [maven-release-plugin][skip ci] prepare for next development iteration 2025-07-23 08:47:11 +00:00
alfresco-build
17eef66f5c [maven-release-plugin][skip ci] prepare release 23.6.0.7 2025-07-23 08:47:08 +00:00
jakubkochman
1d1f269a70 PRODSEC-10304 bumped spring.version to 6.2.8 in 23.N (#3463) 2025-07-23 09:54:15 +02:00
alfresco-build
2ccf6044b8 [maven-release-plugin][skip ci] prepare for next development iteration 2025-07-21 04:28:24 +00:00
alfresco-build
bdd09784e1 [maven-release-plugin][skip ci] prepare release 23.6.0.6 2025-07-21 04:28:22 +00:00
bsayan2
de5d70be46 MNT-25150 check owner aspect first (#3479) 2025-07-18 11:54:26 +05:30
alfresco-build
8cacba0988 [maven-release-plugin][skip ci] prepare for next development iteration 2025-07-02 09:56:47 +00:00
alfresco-build
60187bf9a2 [maven-release-plugin][skip ci] prepare release 23.6.0.5 2025-07-02 09:56:45 +00:00
jakubkochman
ff4634be19 PRODSEC-10332 backport to 23.N (#3445) 2025-07-02 11:04:13 +02:00
alfresco-build
9c64b45908 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-24 06:43:06 +00:00
alfresco-build
d97d8fba04 [maven-release-plugin][skip ci] prepare release 23.6.0.4 2025-06-24 06:43:04 +00:00
KushalBanik
368b571d9c [MNT-10187] common-beanutils bumbed up to 1.11.0 (#3395) 2025-06-24 11:29:03 +05:30
alfresco-build
e9da7d222b [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-17 15:58:11 +00:00
alfresco-build
8c059460f9 [maven-release-plugin][skip ci] prepare release 23.6.0.3 2025-06-17 15:58:09 +00:00
jakubkochman
bd0aaa08b3 ACS-6928 backported to 23.N (#3390) 2025-06-17 17:12:37 +02:00
alfresco-build
93d678dc30 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-05 11:35:36 +00:00
alfresco-build
9648189827 [maven-release-plugin][skip ci] prepare release 23.6.0.2 2025-06-05 11:35:34 +00:00
SatyamSah5
9bfd274127 [ACS-9697] Backport to 23.N (#3375) 2025-06-05 16:23:23 +05:30
jakubkochman
dcf9f65f6b ACS-9646 backport to 23.N (#3378) 2025-06-05 12:12:22 +02:00
alfresco-build
784fae5834 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-03 12:58:29 +00:00
alfresco-build
eddd8a1065 [maven-release-plugin][skip ci] prepare release 23.6.0.1 2025-06-03 12:58:26 +00:00
cezary-witkowski
0941746518 [MNT-24859] Basic Auth still possible with Keycloak enabled (#3373)
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
2025-06-03 14:14:49 +02:00
Damian Ujma
a1f0f35f60 Updating 23.N branch to 23.6.0 after 23.5.0 ACS release [skip ci] 2025-06-03 13:18:18 +02:00
alfresco-build
25d96a50cd [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-02 08:05:26 +00:00
alfresco-build
3c7f024fed [maven-release-plugin][skip ci] prepare release 23.5.0.19 2025-06-02 08:05:25 +00:00
rrajoria
6a1a197701 Bump AOS Version 3.3.0 2025-06-02 12:48:39 +05:30
alfresco-build
5cff5092a0 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-26 16:39:08 +00:00
alfresco-build
b50f912df2 [maven-release-plugin][skip ci] prepare release 23.5.0.18 2025-05-26 16:39:05 +00:00
varapathijanakiram
b4f00dddb8 Merge pull request #3368 from Alfresco/revert-3364-fix/MNT-24776-Backport
Revert "Backported the MNT-24776"
2025-05-26 21:26:29 +05:30
varapathijanakiram
02c103f39a Revert "Backported the MNT-24776 (#3364)"
This reverts commit d86415401d.
2025-05-26 20:47:07 +05:30
alfresco-build
3c23fa20c5 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-23 12:25:05 +00:00
alfresco-build
576b6faac9 [maven-release-plugin][skip ci] prepare release 23.5.0.17 2025-05-23 12:25:03 +00:00
varapathijanakiram
d86415401d Backported the MNT-24776 (#3364) 2025-05-23 17:11:13 +05:30
alfresco-build
460cc1f2cd [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-21 11:36:58 +00:00
alfresco-build
370fef10fd [maven-release-plugin][skip ci] prepare release 23.5.0.16 2025-05-21 11:36:56 +00:00
varapathijanakiram
efadc239d4 Merge pull request #3360 from Alfresco/fix/MNT-24172
[fix/MNT-24172-fixRecreationOfRendition2] Backport Fix of recreation of rendition2
2025-05-21 16:25:03 +05:30
alfresco-build
de90e37578 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-20 11:27:29 +00:00
alfresco-build
6e438d2e4f [maven-release-plugin][skip ci] prepare release 23.5.0.15 2025-05-20 11:27:26 +00:00
SatyamSah5
a86fa21880 [ACS-9572] Show proper message for duplicate unzipping (#3356) 2025-05-20 15:15:42 +05:30
DurgDineshsai
db74a6e7f2 Merge pull request #3357 from Alfresco/fix/MNT-24146
Backport MNT 24146 changes
2025-05-20 11:49:28 +05:30
rrajoria
8c773ac97c fix precommit 2025-05-20 10:53:27 +05:30
vjanakiram
5a2b3cf64d [fix/MNT-24172-fixRecreationOfRendition2] Backport fix for recreation of rendition2 2025-05-19 19:45:06 +05:30
rrajoria
8ab910d2b1 Fix PreCommit 2025-05-19 12:25:24 +05:30
alfresco-build
fa70f1cd45 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-19 05:57:38 +00:00
alfresco-build
75a2e0f901 [maven-release-plugin][skip ci] prepare release 23.5.0.14 2025-05-19 05:57:36 +00:00
bsayan2
84997bcf86 MNT-25069 help documentation url updated. backport to 23.N (#3343) 2025-05-19 10:40:00 +05:30
alfresco-build
536b3ddd6d [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-16 15:13:54 +00:00
alfresco-build
bb86c97b11 [maven-release-plugin][skip ci] prepare release 23.5.0.13 2025-05-16 15:13:52 +00:00
Gerard Olenski
558f117f24 ACS-9578 Improve stability in AddToHoldsBulkV1Tests (#3355)
(cherry picked from commit d163410e3d)
2025-05-16 16:31:44 +02:00
alfresco-build
bbae71658d [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-16 13:04:38 +00:00
DurgDineshsai
71849cd4ac MNT-24146 -backport fix & precommit changes 2025-05-15 17:30:20 +05:30
DurgDineshsai
686ffcb19c MNT-24146 backport fix 2025-05-15 15:04:46 +05:30
136 changed files with 16397 additions and 14998 deletions

View File

@@ -1273,7 +1273,7 @@
"filename": "repository/src/main/resources/alfresco/repository.properties",
"hashed_secret": "84551ae5442affc9f1a2d3b4c86ae8b24860149d",
"is_verified": false,
"line_number": 770,
"line_number": 771,
"is_secret": false
}
],

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<modules>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<modules>

View File

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

View File

@@ -45,7 +45,7 @@ import com.github.dockerjava.netty.NettyDockerCmdExecFactory;
import lombok.Getter;
import lombok.Setter;
import org.alfresco.utility.Utility;
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

@@ -139,7 +139,8 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
ancestorSearchRequest.setQuery(ancestorReq);
STEP("Wait until paths are indexed.");
await().atMost(30, TimeUnit.SECONDS)
// 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);

View File

@@ -42,7 +42,7 @@ import org.alfresco.rest.v0.RMRolesAndActionsAPI;
import org.alfresco.rest.v0.RecordsAPI;
import org.alfresco.rest.v0.RecordCategoriesAPI;
import org.alfresco.test.AlfrescoTest;
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;

View File

@@ -44,7 +44,7 @@ import org.alfresco.rest.v0.service.DispositionScheduleService;
import org.alfresco.test.AlfrescoTest;
import org.alfresco.utility.model.RepoTestModel;
import org.alfresco.utility.model.UserModel;
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

@@ -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)](http://docs.alfresco.com/rm2.4/concepts/welcome-rm.html)
* 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)
***

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](http://docs.alfresco.com/rm-community/concepts/welcome-rm.html)
* [Enterprise Documentation Link](http://docs.alfresco.com/rm/concepts/welcome-rm.html)
* [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)
* [Contribution Model](../../CONTRIBUTING.md)
***
### Prerequisite Knowledge
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:
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:
* [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)
* [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)
***
@@ -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](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/).
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/).
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](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`.
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`.
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](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.
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.
***

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-governance-services-community-parent</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<modules>

View File

@@ -119,6 +119,11 @@ rm.patch.v35.holdNewChildAssocPatch.batchSize=1000
rm.haspermissionmap.read=Read
rm.haspermissionmap.write=WriteProperties,AddChildren,ReadContent
# Extended Permissions
# Enable matching the given username with the correct casing username when retrieving an IPR group.
# Only needs to be used if there are owners that don't have the username in the correct casing.
rm.extendedSecurity.enableUsernameNormalization=false
#
# Extended auto-version behaviour. If true and other auto-version properties are satisfied, then
# a document will be auto-versioned when its type is changed.

View File

@@ -611,6 +611,7 @@
<property name="authorityService" ref="authorityService"/>
<property name="permissionService" ref="permissionService"/>
<property name="transactionService" ref="transactionService"/>
<property name="enableUsernameNormalization" value="${rm.extendedSecurity.enableUsernameNormalization}" />
</bean>
<bean id="ExtendedSecurityService" class="org.springframework.aop.framework.ProxyFactoryBean">

View File

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

View File

@@ -34,6 +34,12 @@ import java.util.HashSet;
import java.util.List;
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;
import org.alfresco.model.RenditionModel;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
@@ -42,7 +48,10 @@ import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService;
import org.alfresco.module.org_alfresco_module_rm.util.ServiceBaseImpl;
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.security.authority.RMAuthority;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -54,12 +63,6 @@ import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.Pair;
import org.alfresco.util.ParameterCheck;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.extensions.webscripts.ui.common.StringUtils;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
/**
* Extended security service implementation.
@@ -68,9 +71,9 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti
* @since 2.1
*/
public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
implements ExtendedSecurityService,
RecordsManagementModel,
ApplicationListener<ContextRefreshedEvent>
implements ExtendedSecurityService,
RecordsManagementModel,
ApplicationListener<ContextRefreshedEvent>
{
/** ipr group names */
static final String ROOT_IPR_GROUP = "INPLACE_RECORD_MANAGEMENT";
@@ -95,8 +98,11 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/** transaction service */
private TransactionService transactionService;
private boolean enableUsernameNormalization;
/**
* @param filePlanService file plan service
* @param filePlanService
* file plan service
*/
public void setFilePlanService(FilePlanService filePlanService)
{
@@ -104,7 +110,8 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
}
/**
* @param filePlanRoleService file plan role service
* @param filePlanRoleService
* file plan role service
*/
public void setFilePlanRoleService(FilePlanRoleService filePlanRoleService)
{
@@ -112,7 +119,8 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
}
/**
* @param authorityService authority service
* @param authorityService
* authority service
*/
public void setAuthorityService(AuthorityService authorityService)
{
@@ -120,7 +128,8 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
}
/**
* @param permissionService permission service
* @param permissionService
* permission service
*/
public void setPermissionService(PermissionService permissionService)
{
@@ -128,13 +137,23 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
}
/**
* @param transactionService transaction service
* @param transactionService
* transaction service
*/
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
/**
* @param enableUsernameNormalization
* enable username normalization to ensure correct casing
*/
public void setEnableUsernameNormalization(boolean enableUsernameNormalization)
{
this.enableUsernameNormalization = enableUsernameNormalization;
}
/**
* Application context refresh event handler
*/
@@ -142,19 +161,17 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent)
{
// run as System on bootstrap
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
AuthenticationUtil.runAs(new RunAsWork<Object>() {
public Object doWork()
{
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>()
{
RetryingTransactionCallback<Void> callback = new RetryingTransactionCallback<Void>() {
public Void execute()
{
// if the root group doesn't exist then create it
if (!authorityService.authorityExists(getRootIRPGroup()))
{
authorityService.createAuthority(AuthorityType.GROUP, ROOT_IPR_GROUP, ROOT_IPR_GROUP,
Collections.singleton(RMAuthority.ZONE_APP_RM));
Collections.singleton(RMAuthority.ZONE_APP_RM));
}
return null;
}
@@ -174,7 +191,7 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
return GROUP_PREFIX + ROOT_IPR_GROUP;
}
/**
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.ExtendedSecurityService#hasExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override
@@ -224,8 +241,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* Helper to get authorities for a given group
*
* @param group group name
* @return Set<String> immediate authorities
* @param group
* group name
* @return Set<String> immediate authorities
*/
private Set<String> getAuthorities(String group)
{
@@ -284,8 +302,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
* <p>
* Return null if none found.
*
* @param nodeRef node reference
* @return Pair<String, String> where first is the read group and second if the write group, null if none found
* @param nodeRef
* node reference
* @return Pair<String, String> where first is the read group and second if the write group, null if none found
*/
private Pair<String, String> getIPRGroups(NodeRef nodeRef)
{
@@ -321,17 +340,17 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* Given a set of readers and writers find or create the appropriate IPR groups.
* <p>
* The IPR groups are named with hashes of the authority lists in order to reduce
* the set of groups that require exact match. A further index is used to handle
* a situation where there is a hash clash, but a difference in the authority lists.
* The IPR groups are named with hashes of the authority lists in order to reduce the set of groups that require exact match. A further index is used to handle a situation where there is a hash clash, but a difference in the authority lists.
* <p>
* When no match is found the groups are created. Once created
* When no match is found the groups are created. Once created
*
* @param filePlan file plan
* @param readers authorities with read
* @param writers authorities with write
* @return Pair<String, String> where first is the full name of the read group and
* second is the full name of the write group
* @param filePlan
* file plan
* @param readers
* authorities with read
* @param writers
* authorities with write
* @return Pair<String, String> where first is the full name of the read group and second is the full name of the write group
*/
private Pair<String, String> createOrFindIPRGroups(Set<String> readers, Set<String> writers)
{
@@ -343,20 +362,28 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* Create or find an IPR group based on the provided prefix and authorities.
*
* @param groupPrefix group prefix
* @param authorities authorities
* @return String full group name
* @param groupPrefix
* group prefix
* @param authorities
* authorities
* @return String full group name
*/
private String createOrFindIPRGroup(String groupPrefix, Set<String> authorities)
{
String group = null;
// If enabled, the authorities are forced to match the correct casing of the usernames in case they were set
// with the incorrect casing.
// If not, it will just use the authorities as they are.
// In normal circumstances, the authorities are in the correct casing, so this is disabled by default.
Set<String> authoritySet = normalizeAuthorities(authorities);
// find group or determine what the next index is if no group exists or there is a clash
Pair<String, Integer> groupResult = findIPRGroup(groupPrefix, authorities);
Pair<String, Integer> groupResult = findIPRGroup(groupPrefix, authoritySet);
if (groupResult.getFirst() == null)
{
group = createIPRGroup(groupPrefix, authorities, groupResult.getSecond());
group = createIPRGroup(groupPrefix, authoritySet, groupResult.getSecond());
}
else
{
@@ -369,13 +396,13 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* Given a group name prefix and the authorities, finds the exact match existing group.
* <p>
* If the group does not exist then the group returned is null and the index shows the next available
* group index for creation.
* If the group does not exist then the group returned is null and the index shows the next available group index for creation.
*
* @param groupPrefix group name prefix
* @param authorities authorities
* @return Pair<String, Integer> where first is the name of the found group, null if none found and second
* if the next available create index
* @param groupPrefix
* group name prefix
* @param authorities
* authorities
* @return Pair<String, Integer> where first is the name of the found group, null if none found and second if the next available create index
*/
private Pair<String, Integer> findIPRGroup(String groupPrefix, Set<String> authorities)
{
@@ -391,12 +418,13 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
while (hasMoreItems == true)
{
// get matching authorities
PagingResults<String> results = authorityService.getAuthorities(AuthorityType.GROUP,
RMAuthority.ZONE_APP_RM,
groupShortNamePrefix,
false,
false,
new PagingRequest(MAX_ITEMS*pageCount, MAX_ITEMS));
PagingResults<String> results = authorityService.getAuthorities(
AuthorityType.GROUP,
RMAuthority.ZONE_APP_RM,
groupShortNamePrefix,
false,
false,
new PagingRequest(MAX_ITEMS * pageCount, MAX_ITEMS));
// record the total count
nextGroupIndex = nextGroupIndex + results.getPage().size();
@@ -413,29 +441,88 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
// determine if there are any more pages to inspect
hasMoreItems = results.hasMoreItems();
pageCount ++;
pageCount++;
}
return new Pair<>(iprGroup, nextGroupIndex);
}
/**
* Given a set of authorities, normalizes the authority names to ensure correct casing.
*
* @param authNames
* @return
*/
private Set<String> normalizeAuthorities(Set<String> authNames)
{
// If disabled or no authorities, return as is
if (!enableUsernameNormalization || authNames == null || authNames.isEmpty())
{
return authNames;
}
Set<String> normalizedAuthorities = new HashSet<>();
for (String authorityName : authNames)
{
normalizedAuthorities.add(normalizeAuthorityName(authorityName));
}
return normalizedAuthorities;
}
/**
* Usernames are case insensitive but affect the IPR group matching when set with different casing. For a given authority of type user, this method normalizes the authority name. If group, it returns the name as-is.
*
* @param authorityName
* the authority name to normalize
* @return the normalized authority name
*/
private String normalizeAuthorityName(String authorityName)
{
if (authorityName == null || authorityName.startsWith(GROUP_PREFIX))
{
return authorityName;
}
// For users, attempt to get the correct casing from the username property of the user node
if (authorityService.authorityExists(authorityName))
{
try
{
NodeRef authorityNodeRef = authorityService.getAuthorityNodeRef(authorityName);
if (authorityNodeRef != null)
{
String username = (String) nodeService.getProperty(authorityNodeRef, ContentModel.PROP_USERNAME);
return username != null ? username : authorityName;
}
}
catch (Exception e)
{
// If anything goes wrong, fallback to the original name
}
}
return authorityName;
}
/**
* Determines whether a group exactly matches a list of authorities.
*
* @param authorities list of authorities
* @param group group
* @param authorities
* list of authorities
* @param group
* group
* @return
*/
private boolean isIPRGroupTrueMatch(String group, Set<String> authorities)
{
//Remove GROUP_EVERYONE for proper comparison as GROUP_EVERYONE is never included in an IPR group
// Remove GROUP_EVERYONE for proper comparison as GROUP_EVERYONE is never included in an IPR group
Set<String> plainAuthorities = new HashSet<String>();
if (authorities != null)
{
plainAuthorities.addAll(authorities);
plainAuthorities.remove(PermissionService.ALL_AUTHORITIES);
}
Set<String> contained = authorityService.getContainedAuthorities(null, group, true);
Set<String> contained = authorityService.getContainedAuthorities(null, group, true);
return contained.equals(plainAuthorities);
}
@@ -444,15 +531,17 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
* <p>
* 'package' scope to help testing.
*
* @param prefix prefix
* @param authorities authorities
* @return String group prefix short name
* @param prefix
* prefix
* @param authorities
* authorities
* @return String group prefix short name
*/
/*package*/ String getIPRGroupPrefixShortName(String prefix, Set<String> authorities)
/* package */ String getIPRGroupPrefixShortName(String prefix, Set<String> authorities)
{
StringBuilder builder = new StringBuilder(128)
.append(prefix)
.append(getAuthoritySetHashCode(authorities));
.append(prefix)
.append(getAuthoritySetHashCode(authorities));
return builder.toString();
}
@@ -464,13 +553,17 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
* <p>
* 'package' scope to help testing.
*
* @param prefix prefix
* @param readers read authorities
* @param writers write authorities
* @param index group index
* @return String group short name
* @param prefix
* prefix
* @param readers
* read authorities
* @param writers
* write authorities
* @param index
* group index
* @return String group short name
*/
/*package*/ String getIPRGroupShortName(String prefix, Set<String> authorities, int index)
/* package */ String getIPRGroupShortName(String prefix, Set<String> authorities, int index)
{
return getIPRGroupShortName(prefix, authorities, Integer.toString(index));
}
@@ -480,17 +573,21 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
* <p>
* Note this excludes the "GROUP_" prefix.
*
* @param prefix prefix
* @param readers read authorities
* @param writers write authorities
* @param index group index
* @return String group short name
* @param prefix
* prefix
* @param readers
* read authorities
* @param writers
* write authorities
* @param index
* group index
* @return String group short name
*/
private String getIPRGroupShortName(String prefix, Set<String> authorities, String index)
{
StringBuilder builder = new StringBuilder(128)
.append(getIPRGroupPrefixShortName(prefix, authorities))
.append(index);
.append(getIPRGroupPrefixShortName(prefix, authorities))
.append(index);
return builder.toString();
}
@@ -498,8 +595,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* Gets the hashcode value of a set of authorities.
*
* @param authorities set of authorities
* @return int hash code
* @param authorities
* set of authorities
* @return int hash code
*/
private int getAuthoritySetHashCode(Set<String> authorities)
{
@@ -514,10 +612,13 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* Creates a new IPR group.
*
* @param groupNamePrefix group name prefix
* @param children child authorities
* @param index group index
* @return String full name of created group
* @param groupNamePrefix
* group name prefix
* @param children
* child authorities
* @param index
* group index
* @return String full name of created group
*/
private String createIPRGroup(String groupNamePrefix, Set<String> children, int index)
{
@@ -547,10 +648,10 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
}
}
}
catch(DuplicateChildNodeNameException ex)
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;
@@ -559,8 +660,10 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* Assign IPR groups to a node reference with the correct permissions.
*
* @param iprGroups iprGroups, first read and second write
* @param nodeRef node reference
* @param iprGroups
* iprGroups, first read and second write
* @param nodeRef
* node reference
*/
private void assignIPRGroupsToNode(Pair<String, String> iprGroups, NodeRef nodeRef)
{
@@ -598,7 +701,8 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* Clear the nodes IPR permissions
*
* @param nodeRef node reference
* @param nodeRef
* node reference
*/
private void clearPermissions(NodeRef nodeRef, Pair<String, String> iprGroups)
{
@@ -610,7 +714,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#getExtendedReaders(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override @Deprecated public Set<String> getExtendedReaders(NodeRef nodeRef)
@Override
@Deprecated
public Set<String> getExtendedReaders(NodeRef nodeRef)
{
return getReaders(nodeRef);
}
@@ -618,7 +724,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#getExtendedWriters(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override @Deprecated public Set<String> getExtendedWriters(NodeRef nodeRef)
@Override
@Deprecated
public Set<String> getExtendedWriters(NodeRef nodeRef)
{
return getWriters(nodeRef);
}
@@ -626,7 +734,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#addExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set)
*/
@Override @Deprecated public void addExtendedSecurity(NodeRef nodeRef, Set<String> readers, Set<String> writers)
@Override
@Deprecated
public void addExtendedSecurity(NodeRef nodeRef, Set<String> readers, Set<String> writers)
{
set(nodeRef, readers, writers);
}
@@ -634,7 +744,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#addExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set, boolean)
*/
@Override @Deprecated public void addExtendedSecurity(NodeRef nodeRef, Set<String> readers, Set<String> writers, boolean applyToParents)
@Override
@Deprecated
public void addExtendedSecurity(NodeRef nodeRef, Set<String> readers, Set<String> writers, boolean applyToParents)
{
set(nodeRef, readers, writers);
}
@@ -642,7 +754,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#removeAllExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef)
*/
@Override @Deprecated public void removeAllExtendedSecurity(NodeRef nodeRef)
@Override
@Deprecated
public void removeAllExtendedSecurity(NodeRef nodeRef)
{
remove(nodeRef);
}
@@ -650,7 +764,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#removeExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set)
*/
@Override @Deprecated public void removeExtendedSecurity(NodeRef nodeRef, Set<String> readers, Set<String> writers)
@Override
@Deprecated
public void removeExtendedSecurity(NodeRef nodeRef, Set<String> readers, Set<String> writers)
{
remove(nodeRef);
}
@@ -658,7 +774,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#removeExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, java.util.Set, java.util.Set, boolean)
*/
@Override @Deprecated public void removeExtendedSecurity(NodeRef nodeRef, Set<String> readers, Set<String>writers, boolean applyToParents)
@Override
@Deprecated
public void removeExtendedSecurity(NodeRef nodeRef, Set<String> readers, Set<String> writers, boolean applyToParents)
{
remove(nodeRef);
}
@@ -666,7 +784,9 @@ public class ExtendedSecurityServiceImpl extends ServiceBaseImpl
/**
* @see org.alfresco.module.org_alfresco_module_rm.security.DeprecatedExtendedSecurityService#removeAllExtendedSecurity(org.alfresco.service.cmr.repository.NodeRef, boolean)
*/
@Override @Deprecated public void removeAllExtendedSecurity(NodeRef nodeRef, boolean applyToParents)
@Override
@Deprecated
public void removeAllExtendedSecurity(NodeRef nodeRef, boolean applyToParents)
{
remove(nodeRef);
}

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;
@@ -73,8 +82,7 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
private String createTestUser()
{
return doTestInTransaction(new Test<String>()
{
return doTestInTransaction(new Test<String>() {
public String run()
{
String userName = GUID.generate();
@@ -90,8 +98,7 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
final String elephant = createTestUser();
final String snake = createTestUser();
doTestInTransaction(new Test<Void>()
{
doTestInTransaction(new Test<Void>() {
public Void run()
{
assertFalse(extendedSecurityService.hasExtendedSecurity(filePlan));
@@ -118,7 +125,7 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
// test remove
extendedSecurityService.remove(recordToo);
assertFalse(extendedSecurityService.hasExtendedSecurity(recordToo));
assertTrue(extendedSecurityService.getReaders(recordToo).isEmpty());
assertTrue(extendedSecurityService.getWriters(recordToo).isEmpty());
@@ -133,8 +140,7 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
final String monkey = createTestUser();
final String elephant = createTestUser();
doTestInTransaction(new Test<Void>()
{
doTestInTransaction(new Test<Void>() {
Set<String> extendedReaders = new HashSet<>(2);
public Void run() throws Exception
@@ -184,112 +190,337 @@ public class ExtendedSecurityServiceImplTest extends BaseRMTestCase
public void testDifferentUsersDifferentPermissions()
{
final String userNone = createTestUser();
final String userRead = createTestUser();
final String userWrite = createTestUser();
final String siteShortName = GUID.generate();
final String userNone = createTestUser();
final String userRead = createTestUser();
final String userWrite = createTestUser();
final String siteShortName = GUID.generate();
doTestInTransaction(new Test<Void>()
{
doTestInTransaction(new Test<Void>() {
public Void run() throws Exception
{
siteService.createSite(null, siteShortName, "test", "test", SiteVisibility.PRIVATE);
return null;
siteService.createSite(null, siteShortName, "test", "test", SiteVisibility.PRIVATE);
return null;
}
});
final NodeRef documentLibrary = doTestInTransaction(new Test<NodeRef>()
{
final NodeRef documentLibrary = doTestInTransaction(new Test<NodeRef>() {
public NodeRef run() throws Exception
{
siteService.setMembership(siteShortName, userRead, SiteModel.SITE_CONSUMER);
siteService.setMembership(siteShortName, userWrite, SiteModel.SITE_COLLABORATOR);
return siteService.createContainer(siteShortName, SiteService.DOCUMENT_LIBRARY, null, null);
siteService.setMembership(siteShortName, userRead, SiteModel.SITE_CONSUMER);
siteService.setMembership(siteShortName, userWrite, SiteModel.SITE_COLLABORATOR);
return siteService.createContainer(siteShortName, SiteService.DOCUMENT_LIBRARY, null, null);
}
});
final NodeRef record = doTestInTransaction(new Test<NodeRef>()
{
final NodeRef record = doTestInTransaction(new Test<NodeRef>() {
public NodeRef run() throws Exception
{
NodeRef record = fileFolderService.create(documentLibrary, GUID.generate(), ContentModel.TYPE_CONTENT).getNodeRef();
recordService.createRecord(filePlan, record);
return record;
NodeRef record = fileFolderService.create(documentLibrary, GUID.generate(), ContentModel.TYPE_CONTENT)
.getNodeRef();
recordService.createRecord(filePlan, record);
return record;
}
});
doTestInTransaction(new Test<Void>()
{
doTestInTransaction(new Test<Void>() {
public Void run() throws Exception
{
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userNone);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userNone);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userRead);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userRead);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, FILING));
return null;
}
}, userWrite);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, FILING));
return null;
}
}, userWrite);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userNone);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userNone);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userRead);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.DENIED, permissionService.hasPermission(record, FILING));
return null;
}
}, userRead);
AuthenticationUtil.runAs(new RunAsWork<Void>()
{
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, FILING));
return null;
}
}, userWrite);
AuthenticationUtil.runAs(new RunAsWork<Void>() {
public Void doWork() throws Exception
{
// check permissions
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, READ_RECORDS));
assertEquals(AccessStatus.ALLOWED, permissionService.hasPermission(record, FILING));
return null;
}
}, userWrite);
return null;
return null;
}
});
}
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

@@ -4,7 +4,7 @@
# Version label
version.major=23
version.minor=5
version.minor=6
version.revision=0
version.label=

View File

@@ -52,6 +52,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.alfresco.model.ContentModel;
import org.alfresco.model.RenditionModel;
import org.alfresco.module.org_alfresco_module_rm.capability.RMPermissionModel;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
@@ -67,6 +68,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransacti
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityService;
@@ -522,6 +524,104 @@ public class ExtendedSecurityServiceImplUnitTest
verify(mockedPermissionService).setPermission(nodeRef, writeGroup, RMPermissionModel.FILING, true);
}
/**
* Given a node with no previous IPR groups assigned
* And having pre-existing IPR groups matching the ones we need
* When I add some read and write authorities but with a different casing
* Then the existing IPR groups are used
*/
@SuppressWarnings("unchecked")
@Test public void addExtendedSecurityWithMixedCasingUsernames()
{
// Have the usernames in the node as the correct usernames but with incorrect casing
String user1 = "UseR";
String user2 = "UseR_w";
// Incorrect IPR Group names
Set<String> diffCasingReaders = Stream.of(user1, GROUP).collect(Collectors.toSet());
Set<String> diffCasingWriters = Stream.of(user2, GROUP_W).collect(Collectors.toSet());
String wrongReadGroupPrefix = extendedSecurityService.getIPRGroupPrefixShortName(READER_GROUP_PREFIX, diffCasingReaders);
String wrongWriteGroupPrefix = extendedSecurityService.getIPRGroupPrefixShortName(WRITER_GROUP_PREFIX, diffCasingWriters);
String wrongReadGroup = wrongReadGroupPrefix + "0";
String wrongWriteGroup = wrongWriteGroupPrefix + "0";
// Correct Group names
String correctReadGroup = readGroupPrefix + "0";
String correctWriteGroup = writeGroupPrefix + "0";
// If queried for the correct groups, return the results
PagingResults<String> mockedCorrectReadPResults = mock(PagingResults.class);
PagingResults<String> mockedCorrectWritePResults = mock(PagingResults.class);
when(mockedCorrectReadPResults.getPage())
.thenReturn(Stream.of(GROUP_PREFIX + correctReadGroup).collect(Collectors.toList()));
when(mockedAuthorityService.getAuthorities(
eq(AuthorityType.GROUP),
eq(RMAuthority.ZONE_APP_RM),
eq(readGroupPrefix),
eq(false),
eq(false),
any(PagingRequest.class)))
.thenReturn(mockedCorrectReadPResults);
when(mockedCorrectWritePResults.getPage())
.thenReturn(Stream.of(GROUP_PREFIX + correctWriteGroup).collect(Collectors.toList()));
when(mockedAuthorityService.getAuthorities(
eq(AuthorityType.GROUP),
eq(RMAuthority.ZONE_APP_RM),
eq(writeGroupPrefix),
eq(false),
eq(false),
any(PagingRequest.class)))
.thenReturn(mockedCorrectWritePResults);
// Don't return results for the incorrect groups (lenient as these may not be called with normalization enabled)
PagingResults<String> mockedWrongReadPResults = mock(PagingResults.class);
PagingResults<String> mockedWrongWritePResults = mock(PagingResults.class);
lenient().when(mockedWrongReadPResults.getPage())
.thenReturn(Collections.emptyList());
lenient().when(mockedAuthorityService.getAuthorities(
eq(AuthorityType.GROUP),
eq(RMAuthority.ZONE_APP_RM),
eq(wrongReadGroupPrefix),
eq(false),
eq(false),
any(PagingRequest.class)))
.thenReturn(mockedWrongReadPResults);
lenient().when(mockedWrongWritePResults.getPage())
.thenReturn(Collections.emptyList());
lenient().when(mockedAuthorityService.getAuthorities(
eq(AuthorityType.GROUP),
eq(RMAuthority.ZONE_APP_RM),
eq(wrongWriteGroupPrefix),
eq(false),
eq(false),
any(PagingRequest.class)))
.thenReturn(mockedWrongWritePResults);
// The users do exist, despite being in a different casing and are able to be retrieved
NodeRef noderefUser1 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, USER);
when(mockedAuthorityService.authorityExists(user1)).thenReturn(true);
when(mockedAuthorityService.getAuthorityNodeRef(user1)).thenReturn(noderefUser1);
when(mockedNodeService.getProperty(noderefUser1, ContentModel.PROP_USERNAME)).thenReturn(USER);
NodeRef noderefUser2 = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, USER_W);
when(mockedAuthorityService.authorityExists(user2)).thenReturn(true);
when(mockedAuthorityService.getAuthorityNodeRef(user2)).thenReturn(noderefUser2);
when(mockedNodeService.getProperty(noderefUser2, ContentModel.PROP_USERNAME)).thenReturn(USER_W);
// Set the extended security service to normalize usernames
extendedSecurityService.setEnableUsernameNormalization(true);
extendedSecurityService.set(nodeRef, diffCasingReaders, diffCasingWriters);
// Verify that the incorrect read group is not created
verify(mockedAuthorityService, never()).createAuthority(AuthorityType.GROUP, wrongReadGroup, wrongReadGroup, Collections.singleton(RMAuthority.ZONE_APP_RM));
// Verify that the incorrect write group is not created
verify(mockedAuthorityService, never()).createAuthority(AuthorityType.GROUP, wrongWriteGroup, wrongWriteGroup, Collections.singleton(RMAuthority.ZONE_APP_RM));
}
/**
* Given a node with no previous IPR groups assigned
@@ -571,7 +671,7 @@ public class ExtendedSecurityServiceImplUnitTest
.thenReturn(Stream
.of(USER_W, AlfMock.generateText())
.collect(Collectors.toSet()));
// add extended security
extendedSecurityService.set(nodeRef, READERS, WRITERS);
@@ -895,7 +995,7 @@ public class ExtendedSecurityServiceImplUnitTest
// group names
String readGroup = extendedSecurityService.getIPRGroupShortName(READER_GROUP_FULL_PREFIX, READERS, 0);
String writeGroup = extendedSecurityService.getIPRGroupShortName(WRITER_GROUP_FULL_PREFIX, WRITERS, 0);
// setup renditions
NodeRef renditionNodeRef = AlfMock.generateNodeRef(mockedNodeService);
when(mockedNodeService.hasAspect(nodeRef, RecordsManagementModel.ASPECT_RECORD))
@@ -904,7 +1004,7 @@ public class ExtendedSecurityServiceImplUnitTest
.thenReturn(renditionNodeRef);
when(mockedNodeService.getChildAssocs(nodeRef, RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL))
.thenReturn(Collections.singletonList(mockedChildAssociationRef));
// setup permissions
Set<AccessPermission> permissions = Stream
.of(new AccessPermissionImpl(AlfMock.generateText(), AccessStatus.ALLOWED, readGroup, 0),
@@ -913,17 +1013,17 @@ public class ExtendedSecurityServiceImplUnitTest
.collect(Collectors.toSet());
when(mockedPermissionService.getAllSetPermissions(nodeRef))
.thenReturn(permissions);
// remove extended security
extendedSecurityService.remove(nodeRef);
// verify that the groups permissions have been removed
verify(mockedPermissionService).clearPermission(nodeRef, readGroup);
verify(mockedPermissionService).clearPermission(nodeRef, writeGroup);
// verify that the groups permissions have been removed from the rendition
verify(mockedPermissionService).clearPermission(renditionNodeRef, readGroup);
verify(mockedPermissionService).clearPermission(renditionNodeRef, writeGroup);
}
}
}

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<modules>

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-amps</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</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

@@ -30,7 +30,7 @@ import java.util.regex.Pattern;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.wiki.WikiPageInfo;
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,28 @@ function doclist_main()
if (logger.isLoggingEnabled())
logger.log("doclist.lib.js - NodeRef: " + parsedArgs.nodeRef + " Query: " + query);
favourites = sanitizeJunkFavouriteKeys(favourites);
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;

View File

@@ -80,6 +80,11 @@ function runAction(p_params)
{
result.fileExist = true;
}
if (error.indexOf("FolderExistsException") != -1)
{
result.fileExist = true;
result.type = "folder";
}
}
results.push(result);

View File

@@ -45,7 +45,7 @@ import org.alfresco.service.cmr.wiki.WikiPageInfo;
import org.alfresco.service.cmr.wiki.WikiService;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.PropertyMap;
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;
@@ -996,7 +996,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);
}
@@ -1006,4 +1006,4 @@ public class WikiRestApiTest extends BaseWebScriptTest
this.wikiService.deleteWikiPage(wikiPageNew);
}
}
}
}

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<dependencies>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<dependencies>

View File

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

View File

@@ -36,8 +36,7 @@ commons-email http://jakarta.apache.org/commons/
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-jxpath 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/

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<properties>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<modules>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</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](http://docs.alfresco.com/5.1/pra/1/topics/cmis-welcome.html).
**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).
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>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<organization>

View File

@@ -16,7 +16,7 @@ import org.alfresco.utility.testrail.annotation.TestRail;
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>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<developers>

View File

@@ -9,7 +9,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</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](http://docs.alfresco.com/5.1/pra/1/topics/pra-welcome.html).
**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).
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](http://docs.alfresco.com/community/concepts/alfresco-sdk-tutorials-using-rest-api-explorer.html) files and provide some usefull information to you like:
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:
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>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<properties>
@@ -17,7 +17,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<rest.api.explorer.branch>master</rest.api.explorer.branch>
<httpclient-osgi-version>4.5.6</httpclient-osgi-version>
<commons-lang3.version>3.17.0</commons-lang3.version>
<commons-lang3.version>3.18.0</commons-lang3.version>
<scribejava-apis.version>8.3.3</scribejava-apis.version>
<java.version>17</java.version>
</properties>

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2022 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
@@ -26,6 +26,13 @@
package org.alfresco.rest.rules;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.ERROR_MESSAGE_ACCESS_RESTRICTED;
import static org.alfresco.rest.actions.access.AccessRestrictionUtil.MAIL_ACTION;
import static org.alfresco.rest.rules.RulesTestsUtils.CHECKIN_ACTION;
@@ -45,11 +52,6 @@ import static org.alfresco.utility.model.FileModel.getRandomFileModel;
import static org.alfresco.utility.model.FileType.TEXT_PLAIN;
import static org.alfresco.utility.model.UserModel.getRandomUserModel;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.junit.Assert.assertEquals;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import java.io.Serializable;
import java.util.Collections;
@@ -61,10 +63,13 @@ import java.util.stream.IntStream;
import jakarta.json.Json;
import jakarta.json.JsonObject;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.alfresco.rest.model.RestActionBodyExecTemplateModel;
import org.alfresco.rest.model.RestActionConstraintModel;
import org.alfresco.rest.model.RestCompositeConditionDefinitionModel;
import org.alfresco.rest.model.RestPaginationModel;
import org.alfresco.rest.model.RestRuleModel;
import org.alfresco.rest.model.RestRuleModelsCollection;
import org.alfresco.utility.constants.UserRole;
@@ -74,9 +79,6 @@ import org.alfresco.utility.model.FolderModel;
import org.alfresco.utility.model.SiteModel;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.model.UserModel;
import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
* Tests for POST /nodes/{nodeId}/rule-sets/{ruleSetId}/rules.
@@ -101,13 +103,13 @@ public class CreateRulesTests extends RulesRestTest
* <p>
* Also check that the isShared field is not returned when not requested.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY})
public void createRule()
{
RestRuleModel ruleModel = rulesUtils.createRuleModelWithModifiedValues();
RestRuleModel rule = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
RestRuleModel expectedRuleModel = rulesUtils.createRuleModelWithModifiedValues();
restClient.assertStatusCodeIs(CREATED);
@@ -117,7 +119,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check creating a rule in a non-existent folder returns an error. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleInNonExistentFolder()
{
STEP("Try to create a rule in non-existent folder.");
@@ -134,7 +136,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check creating a rule in a non-existent rule set returns an error. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleInNonExistentRuleSet()
{
STEP("Try to create a rule in non-existent rule set.");
@@ -148,7 +150,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Try to create a rule without a name and check the error. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleWithEmptyName()
{
RestRuleModel ruleModel = rulesUtils.createRuleModel("");
@@ -160,7 +162,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check we can create two rules with the same name. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void duplicateRuleNameIsAcceptable()
{
RestRuleModel ruleModel = rulesUtils.createRuleModel("duplicateRuleName");
@@ -175,7 +177,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that a user without permission to view the folder cannot create a rule in it. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void requireReadPermissionToCreateRule()
{
STEP("Create a user and use them to create a private site containing a folder");
@@ -194,7 +196,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that a Collaborator cannot create a rule in a folder in a private site. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void siteCollaboratorCannotCreateRule()
{
testRolePermissionsWith(SiteCollaborator);
@@ -204,7 +206,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that a Contributor cannot create a rule in a private folder. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void siteContributorCannotCreateRule()
{
testRolePermissionsWith(SiteContributor);
@@ -214,7 +216,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that a Consumer cannot create a rule in a folder in a private site. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void siteConsumerCannotCreateRule()
{
testRolePermissionsWith(SiteConsumer);
@@ -224,7 +226,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that a siteManager can create a rule in a folder in a private site. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void siteManagerCanCreateRule()
{
testRolePermissionsWith(SiteManager)
@@ -234,7 +236,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check we can't create a rule under a document node. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void tryToCreateRuleUnderDocument()
{
STEP("Create a document.");
@@ -250,7 +252,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check we can create several rules. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRules()
{
STEP("Create a list of rules in one POST request");
@@ -258,19 +260,18 @@ public class CreateRulesTests extends RulesRestTest
List<RestRuleModel> ruleModels = ruleNames.stream().map(rulesUtils::createRuleModel).collect(toList());
RestRuleModelsCollection rules = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createListOfRules(ruleModels);
.createListOfRules(ruleModels);
restClient.assertStatusCodeIs(CREATED);
assertEquals("Unexpected number of rules received in response.", ruleNames.size(), rules.getEntries().size());
IntStream.range(0, ruleModels.size()).forEach(i ->
rules.getEntries().get(i).onModel()
.assertThat().field("id").isNotNull()
.assertThat().field("name").is(ruleNames.get(i)));
IntStream.range(0, ruleModels.size()).forEach(i -> rules.getEntries().get(i).onModel()
.assertThat().field("id").isNotNull()
.assertThat().field("name").is(ruleNames.get(i)));
}
/** Check we can create over 100 rules and get them all back in response. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createOver100Rules()
{
STEP("Create a list of 120 rules in one POST request");
@@ -287,10 +288,9 @@ public class CreateRulesTests extends RulesRestTest
restClient.assertStatusCodeIs(CREATED);
assertEquals("Unexpected number of rules received in response.", ruleCount, rules.getEntries().size());
IntStream.range(0, ruleModels.size()).forEach(i ->
rules.getEntries().get(i).onModel()
.assertThat().field("id").isNotNull()
.assertThat().field("name").is(ruleNamePrefix + (i + 1)));
IntStream.range(0, ruleModels.size()).forEach(i -> rules.getEntries().get(i).onModel()
.assertThat().field("id").isNotNull()
.assertThat().field("name").is(ruleNamePrefix + (i + 1)));
rules.getPagination()
.assertThat().field("count").is(ruleCount)
@@ -302,7 +302,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Try to create several rules with an error in one of them. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRulesWithOneError()
{
STEP("Try to create a three rules but the middle one has an error.");
@@ -319,55 +319,55 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check we can create a rule without description. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleWithoutDescription()
{
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
UserModel admin = dataUser.getAdminUser();
RestRuleModel rule = restClient.authenticateUser(admin).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(CREATED);
rule.assertThat().field("id").isNotNull()
.assertThat().field("name").is(RULE_NAME_DEFAULT)
.assertThat().field("description").isNull();
.assertThat().field("name").is(RULE_NAME_DEFAULT)
.assertThat().field("description").isNull();
}
/** Check we can create a rule without specifying triggers but with the default "inbound" value. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleWithoutTriggers()
{
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
UserModel admin = dataUser.getAdminUser();
RestRuleModel rule = restClient.authenticateUser(admin).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(CREATED);
rule.assertThat().field("id").isNotNull()
.assertThat().field("name").is(RULE_NAME_DEFAULT)
.assertThat().field("triggers").is(List.of("inbound"));
.assertThat().field("name").is(RULE_NAME_DEFAULT)
.assertThat().field("triggers").is(List.of("inbound"));
}
/** Check we can create a rule without error script. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleWithoutErrorScript()
{
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
UserModel admin = dataUser.getAdminUser();
RestRuleModel rule = restClient.authenticateUser(admin).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(CREATED);
rule.assertThat().field("id").isNotNull()
.assertThat().field("name").is(RULE_NAME_DEFAULT)
.assertThat().field("errorScript").isNull();
.assertThat().field("name").is(RULE_NAME_DEFAULT)
.assertThat().field("errorScript").isNull();
}
/** Check we can create a rule with irrelevant isShared flag, and it doesn't have impact to the process. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleWithSharedFlag()
{
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
@@ -375,23 +375,23 @@ public class CreateRulesTests extends RulesRestTest
UserModel admin = dataUser.getAdminUser();
RestRuleModel rule = restClient.authenticateUser(admin).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(CREATED);
rule.assertThat().field("id").isNotNull()
.assertThat().field("name").is(RULE_NAME_DEFAULT)
.assertThat().field("isShared").isNull();
.assertThat().field("name").is(RULE_NAME_DEFAULT)
.assertThat().field("isShared").isNull();
}
/** Check we can create a rule. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES, TestGroup.SANITY})
public void createRuleAndIncludeFieldsInResponse()
{
RestRuleModel ruleModel = rulesUtils.createRuleModel("ruleName");
RestRuleModel rule = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.include("isShared")
.createSingleRule(ruleModel);
.include("isShared")
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(CREATED);
rule.assertThat().field("isShared").isNotNull();
@@ -412,7 +412,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that the folder's owner can create rules, even if it is in a private site they aren't a member of. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void checkOwnerCanCreateRule()
{
STEP("Use admin to create a private site.");
@@ -431,7 +431,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that an administrator can create a rule in a private site even if they aren't a member. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void checkAdminCanCreateRule()
{
STEP("Use a user to create a private site with a folder.");
@@ -446,7 +446,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that a coordinator can create rules in folders outside sites. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void checkCoordinatorCanCreateRule()
{
STEP("Create a folder in the user's file space.");
@@ -454,11 +454,7 @@ public class CreateRulesTests extends RulesRestTest
STEP("Create another user as a coordinator for this folder.");
UserModel coordinator = dataUser.createRandomTestUser("Rules");
/*
Update folder node properties to add a coordinator
{ "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "coordinator.getUsername()",
"name": "Coordinator", "accessStatus":"ALLOWED" } } }
*/
/* Update folder node properties to add a coordinator { "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "coordinator.getUsername()", "name": "Coordinator", "accessStatus":"ALLOWED" } } } */
String putBody = getAddPermissionsBody(coordinator.getUsername(), "Coordinator");
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).updateNode(putBody);
@@ -470,7 +466,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that an editor cannot create rules in folders outside sites. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void checkEditorCannotCreateRule()
{
STEP("Create a folder in the user's file space.");
@@ -478,11 +474,7 @@ public class CreateRulesTests extends RulesRestTest
STEP("Create another user as a editor for this folder.");
UserModel editor = dataUser.createRandomTestUser();
/*
Update folder node properties to add an editor
{ "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "editor.getUsername()",
"name": "Coordinator", "accessStatus":"ALLOWED" } } }
*/
/* Update folder node properties to add an editor { "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "editor.getUsername()", "name": "Coordinator", "accessStatus":"ALLOWED" } } } */
String putBody = getAddPermissionsBody(editor.getUsername(), "Editor");
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).updateNode(putBody);
@@ -494,7 +486,7 @@ public class CreateRulesTests extends RulesRestTest
}
/** Check that a collaborator cannot create rules in folders outside sites. */
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void checkCollaboratorCannotCreateRule()
{
STEP("Create a folder in the user's file space.");
@@ -502,11 +494,7 @@ public class CreateRulesTests extends RulesRestTest
STEP("Create another user as a collaborator for this folder.");
UserModel collaborator = dataUser.createRandomTestUser();
/*
Update folder node properties to add a collaborator
{ "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "collaborator.getUsername()",
"name": "Coordinator", "accessStatus":"ALLOWED" } } }
*/
/* Update folder node properties to add a collaborator { "permissions": { "isInheritanceEnabled": true, "locallySet": { "authorityId": "collaborator.getUsername()", "name": "Coordinator", "accessStatus":"ALLOWED" } } } */
String putBody = getAddPermissionsBody(collaborator.getUsername(), "Collaborator");
restClient.authenticateUser(user).withCoreAPI().usingNode(folder).updateNode(putBody);
@@ -572,10 +560,10 @@ public class CreateRulesTests extends RulesRestTest
public void createRuleWithActions_userCannotUsePrivateAction()
{
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(rulesUtils.createRuleWithPrivateAction());
.createSingleRule(rulesUtils.createRuleWithPrivateAction());
restClient.assertStatusCodeIs(FORBIDDEN)
.assertLastError().containsSummary(ERROR_MESSAGE_ACCESS_RESTRICTED);
.assertLastError().containsSummary(ERROR_MESSAGE_ACCESS_RESTRICTED);
}
/** Check that an administrator can create rules that use private actions. */
@@ -583,7 +571,7 @@ public class CreateRulesTests extends RulesRestTest
public void createRuleWithActions_adminCanUsePrivateAction()
{
restClient.authenticateUser(dataUser.getAdminUser()).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(rulesUtils.createRuleWithPrivateAction());
.createSingleRule(rulesUtils.createRuleWithPrivateAction());
restClient.assertStatusCodeIs(CREATED);
}
@@ -656,8 +644,7 @@ public class CreateRulesTests extends RulesRestTest
public void createRuleWithNotApplicableActionShouldFail()
{
final RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
final RestActionBodyExecTemplateModel invalidAction =
rulesUtils.createCustomActionModel(RulesTestsUtils.DELETE_RENDITION_ACTION, Map.of("dummy-key", "dummy-value"));
final RestActionBodyExecTemplateModel invalidAction = rulesUtils.createCustomActionModel(RulesTestsUtils.DELETE_RENDITION_ACTION, Map.of("dummy-key", "dummy-value"));
ruleModel.setActions(List.of(invalidAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet().createSingleRule(ruleModel);
@@ -673,8 +660,7 @@ public class CreateRulesTests extends RulesRestTest
public void createRuleWithMissingActionParametersShouldFail()
{
final RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
final RestActionBodyExecTemplateModel invalidAction =
rulesUtils.createCustomActionModel(RulesTestsUtils.COPY_ACTION, Collections.emptyMap());
final RestActionBodyExecTemplateModel invalidAction = rulesUtils.createCustomActionModel(RulesTestsUtils.COPY_ACTION, Collections.emptyMap());
ruleModel.setActions(List.of(invalidAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
@@ -736,7 +722,7 @@ public class CreateRulesTests extends RulesRestTest
public void createRuleWithoutMandatoryActionParametersShouldFail()
{
final RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
final RestActionBodyExecTemplateModel invalidAction = rulesUtils.createCustomActionModel(COPY_ACTION, Map.of("deep-copy",false));
final RestActionBodyExecTemplateModel invalidAction = rulesUtils.createCustomActionModel(COPY_ACTION, Map.of("deep-copy", false));
ruleModel.setActions(List.of(invalidAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
@@ -749,7 +735,7 @@ public class CreateRulesTests extends RulesRestTest
/**
* Check we get error when attempting to create a rule that copies files to a non-existent folder.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleThatUsesNonExistentNode()
{
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
@@ -758,16 +744,16 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setActions(List.of(invalidAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(NOT_FOUND);
restClient.assertLastError().containsSummary("The entity with id: non-existent-node was not found");
restClient.assertLastError().containsSummary("Destination folder having Id: non-existent-node no longer exists. Please update your rule definition.");
}
/**
* Check we get error when attempting to create a rule that references a folder that the user does not have read permission for.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleThatUsesNodeWithoutReadPermission()
{
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
@@ -779,7 +765,7 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setActions(List.of(invalidAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(NOT_FOUND);
restClient.assertLastError().containsSummary("The entity with id: " + privateFolder.getNodeRef() + " was not found");
@@ -788,7 +774,7 @@ public class CreateRulesTests extends RulesRestTest
/**
* Check we get error when attempting to create a rule that copies files to a folder that a user only has read permission for.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void createRuleThatWritesToNodeWithoutPermission()
{
SiteModel privateSite = dataSite.usingAdmin().createPrivateRandomSite();
@@ -802,7 +788,7 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setActions(List.of(invalidAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(FORBIDDEN);
restClient.assertLastError().containsSummary("No proper permissions for node: " + privateFolder.getNodeRef());
@@ -828,7 +814,6 @@ public class CreateRulesTests extends RulesRestTest
restClient.assertLastError().containsSummary("Node is not a folder " + fileModel.getNodeRef());
}
/**
* Check we get error when attempting to create a rule with mail action defined with non-existing mail template.
*/
@@ -850,7 +835,7 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setActions(List.of(mailAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(BAD_REQUEST);
restClient.assertLastError().containsSummary("Action parameter: template has invalid value (" + mailTemplate +
@@ -860,7 +845,7 @@ public class CreateRulesTests extends RulesRestTest
/**
* Check the user can create a rule with a script.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void checkCanUseScriptInRule()
{
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
@@ -869,7 +854,7 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setActions(List.of(scriptAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(CREATED);
}
@@ -877,7 +862,7 @@ public class CreateRulesTests extends RulesRestTest
/**
* Check the script has to be stored in the scripts directory in the data dictionary.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void checkCantUseNodeOutsideScriptsDirectory()
{
STEP("Copy script to location outside data dictionary.");
@@ -898,16 +883,16 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setActions(List.of(scriptAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(BAD_REQUEST)
.assertLastError().containsSummary("script-ref has invalid value");
.assertLastError().containsSummary("script-ref has invalid value");
}
/**
* Check a real category needs to be supplied when linking to a category.
*/
@Test (groups = { TestGroup.REST_API, TestGroup.RULES })
@Test(groups = {TestGroup.REST_API, TestGroup.RULES})
public void checkLinkToCategoryNeedsRealCategory()
{
STEP("Attempt to link to a category with a folder node, rather than a category node.");
@@ -918,7 +903,7 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setActions(List.of(categoryAction));
restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
restClient.assertStatusCodeIs(BAD_REQUEST);
}
@@ -933,7 +918,7 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setConditions(rulesUtils.createVariousConditions());
RestRuleModel rule = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
RestRuleModel expectedRuleModel = rulesUtils.createRuleModelWithDefaultValues();
expectedRuleModel.setConditions(rulesUtils.createVariousConditions());
@@ -952,7 +937,7 @@ public class CreateRulesTests extends RulesRestTest
ruleModel.setConditions(rulesUtils.createCompositeCondition(null));
RestRuleModel rule = restClient.authenticateUser(user).withPrivateAPI().usingNode(ruleFolder).usingDefaultRuleSet()
.createSingleRule(ruleModel);
.createSingleRule(ruleModel);
RestRuleModel expectedRuleModel = rulesUtils.createRuleModelWithDefaultValues();
expectedRuleModel.setTriggers(List.of("inbound"));
@@ -969,10 +954,8 @@ public class CreateRulesTests extends RulesRestTest
STEP("Try to create a rule with non existing category in conditions.");
String fakeCategoryId = "bdba5f9f-fake-id22-803b-349bcfd06fd1";
RestCompositeConditionDefinitionModel conditions = rulesUtils.createCompositeCondition(List.of(
rulesUtils.createCompositeCondition(!INVERTED, List.of(
rulesUtils.createSimpleCondition("category", "equals", fakeCategoryId)
))
));
rulesUtils.createCompositeCondition(!INVERTED, List.of(
rulesUtils.createSimpleCondition("category", "equals", fakeCategoryId)))));
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
ruleModel.setConditions(conditions);
@@ -992,9 +975,7 @@ public class CreateRulesTests extends RulesRestTest
final String comparator = "greaterthan";
RestCompositeConditionDefinitionModel conditions = rulesUtils.createCompositeCondition(List.of(
rulesUtils.createCompositeCondition(!INVERTED, List.of(
rulesUtils.createSimpleCondition("size", comparator, "500")
))
));
rulesUtils.createSimpleCondition("size", comparator, "500")))));
RestRuleModel ruleModel = rulesUtils.createRuleModelWithDefaultValues();
ruleModel.setConditions(conditions);

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-packaging</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</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="http://docs.alfresco.com/">Online Documentation</a></p>
<p><a href="https://support.hyland.com/p/alfresco">Online Documentation</a></p>
<p></p>
<%
if (shareServicesModule != null && ModuleInstallState.INSTALLED.equals(shareServicesModule.getInstallState()))

28
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>23.5.0.12</version>
<version>23.6.0.19</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -24,7 +24,7 @@
<properties>
<acs.version.major>23</acs.version.major>
<acs.version.minor>5</acs.version.minor>
<acs.version.minor>6</acs.version.minor>
<acs.version.revision>0</acs.version.revision>
<acs.version.label />
<amp.min.version>${acs.version.major}.0.0</amp.min.version>
@@ -57,13 +57,13 @@
<dependency.acs-event-model.version>0.0.33</dependency.acs-event-model.version>
<dependency.aspectj.version>1.9.22.1</dependency.aspectj.version>
<dependency.spring.version>6.1.14</dependency.spring.version>
<dependency.spring-security.version>6.3.8</dependency.spring-security.version>
<dependency.spring.version>6.2.8</dependency.spring.version>
<dependency.spring-security.version>6.3.9</dependency.spring-security.version>
<dependency.antlr.version>3.5.3</dependency.antlr.version>
<dependency.jackson.version>2.17.2</dependency.jackson.version>
<dependency.cxf.version>4.1.0</dependency.cxf.version>
<dependency.cxf.version>4.1.2</dependency.cxf.version>
<dependency.opencmis.version>1.0.0-jakarta-1</dependency.opencmis.version>
<dependency.webscripts.version>9.4</dependency.webscripts.version>
<dependency.webscripts.version>10.2</dependency.webscripts.version>
<dependency.bouncycastle.version>1.78.1</dependency.bouncycastle.version>
<dependency.mockito-core.version>5.14.1</dependency.mockito-core.version>
<dependency.assertj.version>3.26.3</dependency.assertj.version>
@@ -113,7 +113,7 @@
<dependency.jakarta-json-path.version>2.9.0</dependency.jakarta-json-path.version>
<dependency.json-smart.version>2.5.2</dependency.json-smart.version>
<alfresco.googledrive.version>4.1.0</alfresco.googledrive.version>
<alfresco.aos-module.version>3.3.0-A1</alfresco.aos-module.version>
<alfresco.aos-module.version>3.3.0</alfresco.aos-module.version>
<alfresco.api-explorer.version>23.4.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
<alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
@@ -154,7 +154,7 @@
<connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection>
<developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection>
<url>https://github.com/Alfresco/alfresco-community-repo</url>
<tag>23.5.0.12</tag>
<tag>23.6.0.19</tag>
</scm>
<distributionManagement>
@@ -409,7 +409,7 @@
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
@@ -417,9 +417,9 @@
<version>1.17.1</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>
@@ -439,8 +439,8 @@
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta</artifactId>
<version>2.0.0-M1</version>
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
<version>2.0.0-M4</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<dependencies>

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2023 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
@@ -34,7 +34,7 @@ import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator;
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.web.auth.AuthenticationListener;
import org.alfresco.repo.web.auth.TicketCredentials;
@@ -55,7 +55,7 @@ import java.util.List;
import java.util.Set;
/**
* Authenticator to provide Remote User based Header authentication dropping back to Basic Auth otherwise.
* Authenticator to provide Remote User based Header authentication dropping back to Basic Auth otherwise.
* Statelessly authenticating via a secure header now does not require a Session so can be used with
* request-level load balancers which was not previously possible.
* <p>
@@ -73,9 +73,11 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
protected RemoteUserMapper remoteUserMapper;
protected AuthenticationComponent authenticationComponent;
protected AdminConsoleAuthenticator adminConsoleAuthenticator;
protected ExternalUserAuthenticator adminConsoleAuthenticator;
protected ExternalUserAuthenticator webScriptsHomeAuthenticator;
private boolean alwaysAllowBasicAuthForAdminConsole = true;
private boolean alwaysAllowBasicAuthForWebScriptsHome = true;
List<String> adminConsoleScriptFamilies;
long getRemoteUserTimeoutMilliseconds = GET_REMOTE_USER_TIMEOUT_MILLISECONDS_DEFAULT;
@@ -84,7 +86,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
{
this.remoteUserMapper = remoteUserMapper;
}
public void setAuthenticationComponent(AuthenticationComponent authenticationComponent)
{
this.authenticationComponent = authenticationComponent;
@@ -100,6 +102,16 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
this.alwaysAllowBasicAuthForAdminConsole = alwaysAllowBasicAuthForAdminConsole;
}
public boolean isAlwaysAllowBasicAuthForWebScriptsHome()
{
return alwaysAllowBasicAuthForWebScriptsHome;
}
public void setAlwaysAllowBasicAuthForWebScriptsHome(boolean alwaysAllowBasicAuthForWebScriptsHome)
{
this.alwaysAllowBasicAuthForWebScriptsHome = alwaysAllowBasicAuthForWebScriptsHome;
}
public List<String> getAdminConsoleScriptFamilies()
{
return adminConsoleScriptFamilies;
@@ -121,11 +133,17 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
}
public void setAdminConsoleAuthenticator(
AdminConsoleAuthenticator adminConsoleAuthenticator)
ExternalUserAuthenticator adminConsoleAuthenticator)
{
this.adminConsoleAuthenticator = adminConsoleAuthenticator;
}
public void setWebScriptsHomeAuthenticator(
ExternalUserAuthenticator webScriptsHomeAuthenticator)
{
this.webScriptsHomeAuthenticator = webScriptsHomeAuthenticator;
}
@Override
public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res)
{
@@ -139,11 +157,13 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
*/
public class RemoteUserAuthenticator extends BasicHttpAuthenticator
{
private static final String WEB_SCRIPTS_BASE_PATH = "org/springframework/extensions/webscripts";
public RemoteUserAuthenticator(WebScriptServletRequest req, WebScriptServletResponse res, AuthenticationListener listener)
{
super(req, res, listener);
}
@Override
public boolean authenticate(RequiredAuthentication required, boolean isGuest)
{
@@ -159,24 +179,47 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
{
if (servletReq.getServiceMatch() != null &&
isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
isAdminConsole(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
{
userId = getAdminConsoleUser();
}
else if (servletReq.getServiceMatch() != null &&
isWebScriptsHome(servletReq.getServiceMatch().getWebScript()) && isWebScriptsHomeAuthenticatorActive())
{
userId = getWebScriptsHomeUser();
}
if (userId == null)
{
if (isAlwaysAllowBasicAuthForAdminConsole())
{
final boolean useTimeoutForAdminAccessingAdminConsole = shouldUseTimeoutForAdminAccessingAdminConsole(required, isGuest);
boolean shouldUseTimeout = shouldUseTimeoutForAdminAccessingAdminConsole(required, isGuest);
if (useTimeoutForAdminAccessingAdminConsole && isBasicAuthHeaderPresentForAdmin())
if (shouldUseTimeout && isBasicAuthHeaderPresentForAdmin())
{
return callBasicAuthForAdminConsoleAccess(required, isGuest);
return callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(required, isGuest);
}
try
{
userId = getRemoteUserWithTimeout(useTimeoutForAdminAccessingAdminConsole);
userId = getRemoteUserWithTimeout(shouldUseTimeout);
}
catch (AuthenticationTimeoutException e)
{
// return basic auth challenge
return false;
}
}
else if (isAlwaysAllowBasicAuthForWebScriptsHome())
{
boolean shouldUseTimeout = shouldUseTimeoutForAdminAccessingWebScriptsHome(required, isGuest);
if (shouldUseTimeout && isBasicAuthHeaderPresentForAdmin())
{
return callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(required, isGuest);
}
try
{
userId = getRemoteUserWithTimeout(shouldUseTimeout);
}
catch (AuthenticationTimeoutException e)
{
@@ -255,38 +298,63 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
authenticated = super.authenticate(required, isGuest);
}
}
if(!authenticated && servletReq.getServiceMatch() != null &&
isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
if (!authenticated && servletReq.getServiceMatch() != null)
{
adminConsoleAuthenticator.requestAuthentication(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
WebScript webScript = servletReq.getServiceMatch().getWebScript();
if (isAdminConsole(webScript) && isAdminConsoleAuthenticatorActive())
{
adminConsoleAuthenticator.requestAuthentication(
this.servletReq.getHttpServletRequest(),
this.servletRes.getHttpServletResponse());
}
else if (isWebScriptsHome(webScript)
&& isWebScriptsHomeAuthenticatorActive())
{
webScriptsHomeAuthenticator.requestAuthentication(
this.servletReq.getHttpServletRequest(),
this.servletRes.getHttpServletResponse());
}
}
return authenticated;
}
private boolean callBasicAuthForAdminConsoleAccess(RequiredAuthentication required, boolean isGuest)
private boolean callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(RequiredAuthentication required, boolean isGuest)
{
// return REST call, after a timeout/basic auth challenge
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("An Admin Console request has come in with Basic Auth headers present for an admin user.");
LOGGER.trace("An Admin Console or WebScripts Home request has come in with Basic Auth headers present for an admin user.");
}
// In order to prompt for another password, in case it was not entered correctly,
// the output of this method should be returned by the calling "authenticate" method;
// This would also mean, that once the admin basic auth header is present,
// the authentication chain will not be used for the admin console access
// the authentication chain will not be used for access
return super.authenticate(required, isGuest);
}
private boolean shouldUseTimeoutForAdminAccessingAdminConsole(RequiredAuthentication required, boolean isGuest)
{
boolean useTimeoutForAdminAccessingAdminConsole = RequiredAuthentication.admin.equals(required) && !isGuest &&
servletReq.getServiceMatch() != null && isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript());
boolean adminConsoleTimeout = RequiredAuthentication.admin.equals(required) && !isGuest &&
servletReq.getServiceMatch() != null && isAdminConsole(servletReq.getServiceMatch().getWebScript());
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("Should ensure that the admins can login with basic auth: " + useTimeoutForAdminAccessingAdminConsole);
LOGGER.trace("Should ensure that the admins can login with basic auth: " + adminConsoleTimeout);
}
return useTimeoutForAdminAccessingAdminConsole;
return adminConsoleTimeout;
}
private boolean shouldUseTimeoutForAdminAccessingWebScriptsHome(RequiredAuthentication required, boolean isGuest)
{
boolean adminWebScriptsHomeTimeout = RequiredAuthentication.admin.equals(required) && !isGuest &&
servletReq.getServiceMatch() != null && isWebScriptsHome(servletReq.getServiceMatch().getWebScript());
if (LOGGER.isTraceEnabled())
{
LOGGER.trace("Should ensure that the admins can login with basic auth: " + adminWebScriptsHomeTimeout);
}
return adminWebScriptsHomeTimeout;
}
private boolean isRemoteUserMapperActive()
@@ -299,7 +367,12 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
return adminConsoleAuthenticator != null && (!(adminConsoleAuthenticator instanceof ActivateableBean) || ((ActivateableBean) adminConsoleAuthenticator).isActive());
}
protected boolean isAdminConsoleWebScript(WebScript webScript)
private boolean isWebScriptsHomeAuthenticatorActive()
{
return webScriptsHomeAuthenticator != null && (!(webScriptsHomeAuthenticator instanceof ActivateableBean) || ((ActivateableBean) webScriptsHomeAuthenticator).isActive());
}
protected boolean isAdminConsole(WebScript webScript)
{
if (webScript == null || adminConsoleScriptFamilies == null || webScript.getDescription() == null
|| webScript.getDescription().getFamilys() == null)
@@ -313,7 +386,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
}
// intersect the "family" sets defined
Set<String> families = new HashSet<String>(webScript.getDescription().getFamilys());
Set<String> families = new HashSet<>(webScript.getDescription().getFamilys());
families.retainAll(adminConsoleScriptFamilies);
final boolean isAdminConsole = !families.isEmpty();
@@ -325,6 +398,23 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
return isAdminConsole;
}
protected boolean isWebScriptsHome(WebScript webScript)
{
if (webScript == null || webScript.toString() == null)
{
return false;
}
boolean isWebScriptsHome = webScript.toString().startsWith(WEB_SCRIPTS_BASE_PATH);
if (LOGGER.isTraceEnabled() && isWebScriptsHome)
{
LOGGER.trace("Detected a WebScripts Home webscript: " + webScript);
}
return isWebScriptsHome;
}
protected String getRemoteUserWithTimeout(boolean useTimeout) throws AuthenticationTimeoutException
{
if (!useTimeout)
@@ -394,7 +484,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
protected String getRemoteUser()
{
String userId = null;
// If the remote user mapper is configured, we may be able to map in an externally authenticated user
if (isRemoteUserMapperActive())
{
@@ -423,7 +513,21 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
if (isRemoteUserMapperActive())
{
userId = adminConsoleAuthenticator.getAdminConsoleUser(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
userId = adminConsoleAuthenticator.getUserId(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
}
logRemoteUserID(userId);
return userId;
}
protected String getWebScriptsHomeUser()
{
String userId = null;
if (isRemoteUserMapperActive())
{
userId = webScriptsHomeAuthenticator.getUserId(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
}
logRemoteUserID(userId);

View File

@@ -28,11 +28,9 @@ package org.alfresco.repo.web.scripts.transfer;
import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.service.cmr.transfer.TransferException;
import org.alfresco.service.cmr.transfer.TransferReceiver;
import org.apache.commons.fileupload2.core.FileItemInput;
import org.apache.commons.fileupload2.core.FileItemInputIterator;
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.Status;
@@ -41,6 +39,9 @@ import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.extensions.webscripts.WrappingWebScriptRequest;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.alfresco.service.cmr.transfer.TransferException;
import org.alfresco.service.cmr.transfer.TransferReceiver;
/**
* This command processor is used to receive one or more content files for a given transfer.
*
@@ -50,9 +51,9 @@ import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest
public class PostContentCommandProcessor implements CommandProcessor
{
private TransferReceiver receiver;
private static final String MSG_CAUGHT_UNEXPECTED_EXCEPTION = "transfer_service.receiver.caught_unexpected_exception";
private static Log logger = LogFactory.getLog(PostContentCommandProcessor.class);
/**
@@ -64,12 +65,9 @@ public class PostContentCommandProcessor implements CommandProcessor
this.receiver = receiver;
}
/*
* (non-Javadoc)
/* (non-Javadoc)
*
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest,
* org.alfresco.web.scripts.WebScriptResponse)
*/
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */
public int process(WebScriptRequest req, WebScriptResponse resp)
{
logger.debug("post content start");
@@ -91,8 +89,7 @@ public class PostContentCommandProcessor implements CommandProcessor
{
current = null;
}
}
while (current != null);
} while (current != null);
if (webScriptServletRequest == null)
{
resp.setStatus(Status.STATUS_BAD_REQUEST);
@@ -101,7 +98,7 @@ public class PostContentCommandProcessor implements CommandProcessor
HttpServletRequest servletRequest = webScriptServletRequest.getHttpServletRequest();
//Read the transfer id from the request
// Read the transfer id from the request
String transferId = servletRequest.getParameter("transferId");
if ((transferId == null) || !JakartaServletFileUpload.isMultipartContent(servletRequest))
@@ -124,34 +121,34 @@ public class PostContentCommandProcessor implements CommandProcessor
logger.debug("got content Mime Part : " + name);
receiver.saveContent(transferId, item.getName(), item.getInputStream());
}
}
// WebScriptServletRequest alfRequest = (WebScriptServletRequest)req;
// String[] names = alfRequest.getParameterNames();
// for(String name : names)
// {
// FormField item = alfRequest.getFileField(name);
//
// if(item != null)
// {
// logger.debug("got content Mime Part : " + name);
// receiver.saveContent(transferId, item.getName(), item.getInputStream());
// }
// else
// {
// //TODO - should this be an exception?
// logger.debug("Unable to get content for Mime Part : " + name);
// }
// }
}
// WebScriptServletRequest alfRequest = (WebScriptServletRequest)req;
// String[] names = alfRequest.getParameterNames();
// for(String name : names)
// {
// FormField item = alfRequest.getFileField(name);
//
// if(item != null)
// {
// logger.debug("got content Mime Part : " + name);
// receiver.saveContent(transferId, item.getName(), item.getInputStream());
// }
// else
// {
// //TODO - should this be an exception?
// logger.debug("Unable to get content for Mime Part : " + name);
// }
// }
logger.debug("success");
resp.setStatus(Status.STATUS_OK);
}
}
catch (Exception ex)
{
logger.debug("exception caught", ex);
if(transferId != null)
if (transferId != null)
{
logger.debug("ending transfer", ex);
receiver.end(transferId);

View File

@@ -27,15 +27,11 @@
package org.alfresco.repo.web.scripts.transfer;
import java.io.OutputStream;
import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.repo.transfer.TransferCommons;
import org.alfresco.service.cmr.transfer.TransferException;
import org.alfresco.service.cmr.transfer.TransferReceiver;
import org.apache.commons.fileupload2.core.FileItemInput;
import org.apache.commons.fileupload2.core.FileItemInputIterator;
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.Status;
@@ -44,6 +40,10 @@ import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.extensions.webscripts.WrappingWebScriptRequest;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.alfresco.repo.transfer.TransferCommons;
import org.alfresco.service.cmr.transfer.TransferException;
import org.alfresco.service.cmr.transfer.TransferReceiver;
/**
* This command processor is used to receive the snapshot for a given transfer.
*
@@ -53,17 +53,17 @@ import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest
public class PostSnapshotCommandProcessor implements CommandProcessor
{
private TransferReceiver receiver;
private static Log logger = LogFactory.getLog(PostSnapshotCommandProcessor.class);
private static final String MSG_CAUGHT_UNEXPECTED_EXCEPTION = "transfer_service.receiver.caught_unexpected_exception";
/* (non-Javadoc)
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse)
*/
*
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */
public int process(WebScriptRequest req, WebScriptResponse resp)
{
int result = Status.STATUS_OK;
// Unwrap to a WebScriptServletRequest if we have one
WebScriptServletRequest webScriptServletRequest = null;
@@ -83,45 +83,44 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
{
current = null;
}
}
while (current != null);
if (webScriptServletRequest == null)
} while (current != null);
if (webScriptServletRequest == null)
{
logger.debug("bad request, not assignable from");
resp.setStatus(Status.STATUS_BAD_REQUEST);
return Status.STATUS_BAD_REQUEST;
}
//We can't use the WebScriptRequest version of getParameter, since that may cause the content stream
//to be parsed. Get hold of the raw HttpServletRequest and work with that.
// We can't use the WebScriptRequest version of getParameter, since that may cause the content stream
// to be parsed. Get hold of the raw HttpServletRequest and work with that.
HttpServletRequest servletRequest = webScriptServletRequest.getHttpServletRequest();
//Read the transfer id from the request
// Read the transfer id from the request
String transferId = servletRequest.getParameter("transferId");
if ((transferId == null) || !JakartaServletFileUpload.isMultipartContent(servletRequest))
{
logger.debug("bad request, not multipart");
resp.setStatus(Status.STATUS_BAD_REQUEST);
return Status.STATUS_BAD_REQUEST;
}
try
try
{
logger.debug("about to upload manifest file");
JakartaServletFileUpload upload = new JakartaServletFileUpload();
FileItemInputIterator iter = upload.getItemIterator(servletRequest);
while (iter.hasNext())
while (iter.hasNext())
{
FileItemInput item = iter.next();
if (!item.isFormField() && TransferCommons.PART_NAME_MANIFEST.equals(item.getFieldName()))
if (!item.isFormField() && TransferCommons.PART_NAME_MANIFEST.equals(item.getFieldName()))
{
logger.debug("got manifest file");
receiver.saveSnapshot(transferId, item.getInputStream());
}
}
logger.debug("success");
resp.setStatus(Status.STATUS_OK);
@@ -133,10 +132,10 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
receiver.generateRequsite(transferId, out);
}
}
catch (Exception ex)
catch (Exception ex)
{
logger.debug("exception caught", ex);
if(transferId != null)
if (transferId != null)
{
logger.debug("ending transfer", ex);
receiver.end(transferId);
@@ -151,7 +150,8 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
}
/**
* @param receiver the receiver to set
* @param receiver
* the receiver to set
*/
public void setReceiver(TransferReceiver receiver)
{

View File

@@ -2,23 +2,23 @@
* #%L
* Alfresco Remote API
* %%
* 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
* the paid license agreement will prevail. Otherwise, the software is
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
@@ -38,11 +38,19 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.surf.util.I18NUtil;
import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingRequest;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.People;
@@ -77,8 +85,6 @@ import org.alfresco.util.AlfrescoCollator;
import org.alfresco.util.ISO9075;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.SearchLanguageConversion;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Queries implementation
@@ -88,43 +94,43 @@ import org.springframework.extensions.surf.util.I18NUtil;
*/
public class QueriesImpl implements Queries, InitializingBean
{
private final static Map<String,QName> NODE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
PARAM_NAME, ContentModel.PROP_NAME,
PARAM_CREATEDAT, ContentModel.PROP_CREATED,
PARAM_MODIFIEDAT, ContentModel.PROP_MODIFIED);
private static final Log LOGGER = LogFactory.getLog(QueriesImpl.class);
private final static Map<String, QName> NODE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
PARAM_NAME, ContentModel.PROP_NAME,
PARAM_CREATEDAT, ContentModel.PROP_CREATED,
PARAM_MODIFIEDAT, ContentModel.PROP_MODIFIED);
private final static Map<String, QName> PEOPLE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
PARAM_PERSON_ID, ContentModel.PROP_USERNAME,
ContentModel.PROP_FIRSTNAME,
ContentModel.PROP_LASTNAME);
PARAM_PERSON_ID, ContentModel.PROP_USERNAME,
ContentModel.PROP_FIRSTNAME,
ContentModel.PROP_LASTNAME);
private final static Map<String, QName> SITE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
PARAM_SITE_ID, ContentModel.PROP_NAME,
PARAM_SITE_TITLE, ContentModel.PROP_TITLE,
PARAM_SITE_ID, ContentModel.PROP_NAME,
PARAM_SITE_TITLE, ContentModel.PROP_TITLE,
PARAM_SITE_DESCRIPTION, ContentModel.PROP_DESCRIPTION);
/**
* Helper method to build a map of sort parameter names to QNames. This method iterates through
* the parameters. If a parameter is a String it is assumed to be a sort parameter name and will
* be followed by a QName to which it maps. If however it is a QName the local name of the OName
* is used as the sort parameter name.
* @param parameters to build up the map.
* Helper method to build a map of sort parameter names to QNames. This method iterates through the parameters. If a parameter is a String it is assumed to be a sort parameter name and will be followed by a QName to which it maps. If however it is a QName the local name of the OName is used as the sort parameter name.
*
* @param parameters
* to build up the map.
* @return the map
*/
private static Map<String, QName> sortParamsToQNames(Object... parameters)
{
Map<String, QName> map = new HashMap<>();
for (int i=0; i<parameters.length; i++)
for (int i = 0; i < parameters.length; i++)
{
map.put(
parameters[i] instanceof String
? (String)parameters[i++]
: ((QName)parameters[i]).getLocalName(),
(QName)parameters[i]);
parameters[i] instanceof String
? (String) parameters[i++]
: ((QName) parameters[i]).getLocalName(),
(QName) parameters[i]);
}
return Collections.unmodifiableMap(map);
}
private ServiceRegistry sr;
private NodeService nodeService;
private NamespaceService namespaceService;
@@ -162,7 +168,7 @@ public class QueriesImpl implements Queries, InitializingBean
ParameterCheck.mandatory("nodes", this.nodes);
ParameterCheck.mandatory("people", this.people);
ParameterCheck.mandatory("sites", this.sites);
this.nodeService = sr.getNodeService();
this.namespaceService = sr.getNamespaceService();
this.dictionaryService = sr.getDictionaryService();
@@ -173,10 +179,9 @@ public class QueriesImpl implements Queries, InitializingBean
public CollectionWithPagingInfo<Node> findNodes(Parameters parameters)
{
SearchService searchService = sr.getSearchService();
return new AbstractQuery<Node>(nodeService, searchService)
{
return new AbstractQuery<Node>(nodeService, searchService) {
private final Map<String, UserInfo> mapUserInfo = new HashMap<>(10);
@Override
protected void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName)
{
@@ -198,14 +203,14 @@ public class QueriesImpl implements Queries, InitializingBean
{
query.append(")");
}
String nodeTypeStr = parameters.getParameter(PARAM_NODE_TYPE);
if (nodeTypeStr != null)
{
QName filterNodeTypeQName = nodes.createQName(nodeTypeStr);
if (dictionaryService.getType(filterNodeTypeQName) == null)
{
throw new InvalidArgumentException("Unknown filter nodeType: "+nodeTypeStr);
throw new InvalidArgumentException("Unknown filter nodeType: " + nodeTypeStr);
}
query.append(" AND (+TYPE:\"").append(nodeTypeStr).append(("\")"));
@@ -229,7 +234,7 @@ public class QueriesImpl implements Queries, InitializingBean
Path path = null;
try
{
path = nodeService.getPath(nodeRef);
path = nodeService.getPath(nodeRef);
}
catch (InvalidNodeRefException inre)
{
@@ -248,7 +253,7 @@ public class QueriesImpl implements Queries, InitializingBean
{
// first request for this namespace prefix, get and cache result
Collection<String> prefixes = namespaceService.getPrefixes(qname.getNamespaceURI());
prefix = prefixes.size() != 0 ? prefixes.iterator().next() : "";
prefix = !prefixes.isEmpty() ? prefixes.iterator().next() : "";
cache.put(qname.getNamespaceURI(), prefix);
}
buf.append('/').append(prefix).append(':').append(ISO9075.encode(qname.getLocalName()));
@@ -262,12 +267,6 @@ public class QueriesImpl implements Queries, InitializingBean
return buf.toString();
}
@Override
protected List<Node> newList(int capacity)
{
return new ArrayList<Node>(capacity);
}
@Override
protected Node convert(NodeRef nodeRef, List<String> includeParam)
{
@@ -281,18 +280,17 @@ public class QueriesImpl implements Queries, InitializingBean
term = SearchLanguageConversion.escapeLuceneQuery(term);
return term;
}
}.find(parameters, PARAM_TERM, MIN_TERM_LENGTH_NODES, "keywords",
IN_QUERY_SORT, NODE_SORT_PARAMS_TO_QNAMES,
new SortColumn(PARAM_MODIFIEDAT, false));
IN_QUERY_SORT, NODE_SORT_PARAMS_TO_QNAMES,
new SortColumn(PARAM_MODIFIEDAT, false));
}
@Override
public CollectionWithPagingInfo<Person> findPeople(Parameters parameters)
{
SearchService searchService = sr.getSearchService();
return new AbstractQuery<Person>(nodeService, searchService)
{
return new AbstractQuery<Person>(nodeService, searchService) {
@Override
protected void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName)
{
@@ -305,33 +303,25 @@ public class QueriesImpl implements Queries, InitializingBean
query.append("*\")");
}
@Override
protected List<Person> newList(int capacity)
{
return new ArrayList<Person>(capacity);
}
@Override
protected Person convert(NodeRef nodeRef, List<String> includeParam)
{
String personId = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME);
Person person = people.getPerson(personId);
return person;
return people.getPerson(personId);
}
// TODO Do the sort in the query on day. A comment in the code for the V0 API used for live people
// search says adding sort values for this query don't work - tried it and they really don't.
// search says adding sort values for this query don't work - tried it and they really don't.
}.find(parameters, PARAM_TERM, MIN_TERM_LENGTH_PEOPLE, "_PERSON",
POST_QUERY_SORT, PEOPLE_SORT_PARAMS_TO_QNAMES,
new SortColumn(PARAM_FIRSTNAME, true), new SortColumn(PARAM_LASTNAME, true));
POST_QUERY_SORT, PEOPLE_SORT_PARAMS_TO_QNAMES,
new SortColumn(PARAM_FIRSTNAME, true), new SortColumn(PARAM_LASTNAME, true));
}
@Override
public CollectionWithPagingInfo<Site> findSites(Parameters parameters)
{
SearchService searchService = sr.getSearchService();
return new AbstractQuery<Site>(nodeService, searchService)
{
return new AbstractQuery<Site>(nodeService, searchService) {
@Override
protected void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName)
{
@@ -343,44 +333,34 @@ public class QueriesImpl implements Queries, InitializingBean
query.append(term);
query.append("*\")");
}
@Override
protected List<Site> newList(int capacity)
{
return new ArrayList<>(capacity);
}
@Override
protected Site convert(NodeRef nodeRef, List<String> includeParam)
{
return getSite(siteService.getSite(nodeRef), true);
return getSite(siteService.getSite(nodeRef));
}
// note: see also Sites.getSite
private Site getSite(SiteInfo siteInfo, boolean includeRole)
private Site getSite(SiteInfo siteInfo)
{
// set the site id to the short name (to deal with case sensitivity issues with using the siteId from the url)
String siteId = siteInfo.getShortName();
String role = null;
if(includeRole)
{
role = sites.getSiteRole(siteId);
}
String role = sites.getSiteRole(siteId);
return new Site(siteInfo, role);
}
}.find(parameters, PARAM_TERM, MIN_TERM_LENGTH_SITES, "_SITE", POST_QUERY_SORT, SITE_SORT_PARAMS_TO_QNAMES, new SortColumn(PARAM_SITE_TITLE, true));
}
public abstract static class AbstractQuery<T>
{
public enum Sort
{
IN_QUERY_SORT, POST_QUERY_SORT
}
private final NodeService nodeService;
private final SearchService searchService;
public AbstractQuery(NodeService nodeService, SearchService searchService)
{
this.nodeService = nodeService;
@@ -388,14 +368,14 @@ public class QueriesImpl implements Queries, InitializingBean
}
public CollectionWithPagingInfo<T> find(Parameters parameters,
String termName, int minTermLength, String queryTemplateName,
Sort sort, Map<String, QName> sortParamsToQNames, SortColumn... defaultSort)
String termName, int minTermLength, String queryTemplateName,
Sort sort, Map<String, QName> sortParamsToQNames, SortColumn... defaultSort)
{
SearchParameters sp = new SearchParameters();
sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO);
sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
sp.setDefaultFieldName(queryTemplateName);
String term = getTerm(parameters, termName, minTermLength);
StringBuilder query = new StringBuilder();
@@ -404,7 +384,7 @@ public class QueriesImpl implements Queries, InitializingBean
Paging paging = parameters.getPaging();
PagingRequest pagingRequest = Util.getPagingRequest(paging);
List<SortColumn> defaultSortCols = (defaultSort != null ? Arrays.asList(defaultSort) : Collections.emptyList());
if (sort == IN_QUERY_SORT)
{
@@ -413,36 +393,43 @@ public class QueriesImpl implements Queries, InitializingBean
sp.setSkipCount(pagingRequest.getSkipCount());
sp.setMaxItems(pagingRequest.getMaxItems());
}
ResultSet queryResults = null;
List<T> collection = null;
try
{
queryResults = searchService.query(sp);
List<NodeRef> nodeRefs = queryResults.getNodeRefs();
if (sort == POST_QUERY_SORT)
{
nodeRefs = postQuerySort(parameters, sortParamsToQNames, defaultSortCols, nodeRefs);
}
collection = newList(nodeRefs.size());
Map<NodeRef, T>
collection = new LinkedHashMap<>(nodeRefs.size());
List<String> includeParam = parameters.getInclude();
for (NodeRef nodeRef : nodeRefs)
{
T t = convert(nodeRef, includeParam);
collection.add(t);
try
{
T t = convert(nodeRef, includeParam);
collection.put(nodeRef, t);
}
catch (AccessDeniedException ade)
{
LOGGER.debug("Ignoring search result for nodeRef " + nodeRef + " due to access denied exception", ade);
}
}
if (sort == POST_QUERY_SORT)
{
return listPage(collection, paging);
List<T> postQuerySortedCollection = postQuerySort(parameters, sortParamsToQNames, defaultSortCols, collection.keySet())
.stream()
.map(collection::get)
.toList();
return listPage(postQuerySortedCollection, paging);
}
else
{
return CollectionWithPagingInfo.asPaged(paging, collection, queryResults.hasMore(), Long.valueOf(queryResults.getNumberFound()).intValue());
return CollectionWithPagingInfo.asPaged(paging, collection.values(), queryResults.hasMore(), Long.valueOf(queryResults.getNumberFound()).intValue());
}
}
finally
@@ -455,40 +442,39 @@ public class QueriesImpl implements Queries, InitializingBean
}
/**
* Builds up the query and is expected to call {@link SearchParameters#setDefaultFieldName(String)}
* and {@link SearchParameters#addQueryTemplate(String, String)}
* @param query StringBuilder into which the query should be built.
* @param term to be searched for
* @param sp SearchParameters
* Builds up the query and is expected to call {@link SearchParameters#setDefaultFieldName(String)} and {@link SearchParameters#addQueryTemplate(String, String)}
*
* @param query
* StringBuilder into which the query should be built.
* @param term
* to be searched for
* @param sp
* SearchParameters
* @param queryTemplateName
*/
protected abstract void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName);
/**
* Returns a list of the correct type.
* @param capacity of the list
* @return a new list.
*/
protected abstract List<T> newList(int capacity);
/**
* Converts a nodeRef into the an object of the required type.
* @param nodeRef to be converted
* @param includeParam additional fields to be included
*
* @param nodeRef
* to be converted
* @param includeParam
* additional fields to be included
* @return the object
*/
protected abstract T convert(NodeRef nodeRef, List<String> includeParam);
protected String getTerm(Parameters parameters, String termName, int minTermLength)
{
String term = parameters.getParameter(termName);
if (term == null)
{
throw new InvalidArgumentException("Query '"+termName+"' not specified");
throw new InvalidArgumentException("Query '" + termName + "' not specified");
}
term = escapeTerm(term);
int cnt = 0;
for (int i = 0; i < term.length(); i++)
{
@@ -505,7 +491,7 @@ public class QueriesImpl implements Queries, InitializingBean
if (cnt < minTermLength)
{
throw new InvalidArgumentException("Query '"+termName+"' is too short. Must have at least "+minTermLength+" alphanumeric chars");
throw new InvalidArgumentException("Query '" + termName + "' is too short. Must have at least " + minTermLength + " alphanumeric chars");
}
return term;
@@ -524,12 +510,12 @@ public class QueriesImpl implements Queries, InitializingBean
term = SearchLanguageConversion.escapeLuceneQuery(term);
return term;
}
/**
* Adds sort order to the SearchParameters.
*/
protected void addSortOrder(Parameters parameters, Map<String, QName> sortParamsToQNames,
List<SortColumn> defaultSortCols, SearchParameters sp)
List<SortColumn> defaultSortCols, SearchParameters sp)
{
List<SortColumn> sortCols = getSorting(parameters, defaultSortCols);
for (SortColumn sortCol : sortCols)
@@ -537,16 +523,16 @@ public class QueriesImpl implements Queries, InitializingBean
QName sortPropQName = sortParamsToQNames.get(sortCol.column);
if (sortPropQName == null)
{
throw new InvalidArgumentException("Invalid sort field: "+sortCol.column);
throw new InvalidArgumentException("Invalid sort field: " + sortCol.column);
}
sp.addSort("@" + sortPropQName, sortCol.asc);
sp.addSort("@" + sortPropQName, sortCol.asc);
}
}
private List<SortColumn> getSorting(Parameters parameters, List<SortColumn> defaultSortCols)
{
List<SortColumn> sortCols = parameters.getSorting();
if (sortCols == null || sortCols.size() == 0)
if (sortCols == null || sortCols.isEmpty())
{
sortCols = defaultSortCols == null ? Collections.emptyList() : defaultSortCols;
}
@@ -554,63 +540,66 @@ public class QueriesImpl implements Queries, InitializingBean
}
protected List<NodeRef> postQuerySort(Parameters parameters, Map<String, QName> sortParamsToQNames,
List<SortColumn> defaultSortCols, List<NodeRef> nodeRefs)
List<SortColumn> defaultSortCols, Set<NodeRef> unsortedNodeRefs)
{
final List<SortColumn> sortCols = getSorting(parameters, defaultSortCols);
int sortColCount = sortCols.size();
if (sortColCount > 0)
{
// make copy of nodeRefs because it can be unmodifiable list.
nodeRefs = new ArrayList<NodeRef>(nodeRefs);
List<QName> sortPropQNames = new ArrayList<>(sortColCount);
for (SortColumn sortCol : sortCols)
{
QName sortPropQName = sortParamsToQNames.get(sortCol.column);
if (sortPropQName == null)
{
throw new InvalidArgumentException("Invalid sort field: "+sortCol.column);
}
sortPropQNames.add(sortPropQName);
}
final Collator col = AlfrescoCollator.getInstance(I18NUtil.getLocale());
Collections.sort(nodeRefs, new Comparator<NodeRef>()
{
@Override
public int compare(NodeRef n1, NodeRef n2)
{
int result = 0;
for (int i=0; i<sortCols.size(); i++)
{
SortColumn sortCol = sortCols.get(i);
QName sortPropQName = sortPropQNames.get(i);
Serializable p1 = getProperty(n1, sortPropQName);
Serializable p2 = getProperty(n2, sortPropQName);
result = ((p1 instanceof Long) && (p2 instanceof Long)
? Long.compare((Long)p1, (Long)p2)
if (sortColCount == 0)
{
return new ArrayList<>(unsortedNodeRefs);
}
// make copy of nodeRefs because it can be unmodifiable list.
List<NodeRef> sortedNodeRefs = new ArrayList<>(unsortedNodeRefs);
List<QName> sortPropQNames = new ArrayList<>(sortColCount);
for (SortColumn sortCol : sortCols)
{
QName sortPropQName = sortParamsToQNames.get(sortCol.column);
if (sortPropQName == null)
{
throw new InvalidArgumentException("Invalid sort field: " + sortCol.column);
}
sortPropQNames.add(sortPropQName);
}
final Collator col = AlfrescoCollator.getInstance(I18NUtil.getLocale());
Collections.sort(sortedNodeRefs, new Comparator<NodeRef>() {
@Override
public int compare(NodeRef n1, NodeRef n2)
{
int result = 0;
for (int i = 0; i < sortCols.size(); i++)
{
SortColumn sortCol = sortCols.get(i);
QName sortPropQName = sortPropQNames.get(i);
Serializable p1 = getProperty(n1, sortPropQName);
Serializable p2 = getProperty(n2, sortPropQName);
result = ((p1 instanceof Long) && (p2 instanceof Long)
? Long.compare((Long) p1, (Long) p2)
: col.compare(p1.toString(), p2.toString()))
* (sortCol.asc ? 1 : -1);
if (result != 0)
{
break;
}
if (result != 0)
{
break;
}
return result;
}
return result;
}
private Serializable getProperty(NodeRef nodeRef, QName sortPropQName)
{
Serializable result = nodeService.getProperty(nodeRef, sortPropQName);
return result == null ? "" : result;
}
private Serializable getProperty(NodeRef nodeRef, QName sortPropQName)
{
Serializable result = nodeService.getProperty(nodeRef, sortPropQName);
return result == null ? "" : result;
}
});
}
return nodeRefs;
});
return sortedNodeRefs;
}
// note: see also AbstractNodeRelation

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2022 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
@@ -39,6 +39,9 @@ import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.collections.MapUtils;
import org.apache.logging.log4j.util.Strings;
import org.alfresco.repo.action.executer.CheckOutActionExecuter;
import org.alfresco.repo.action.executer.CopyActionExecuter;
import org.alfresco.repo.action.executer.ImageTransformActionExecuter;
@@ -58,8 +61,6 @@ import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.apache.commons.collections.MapUtils;
import org.apache.logging.log4j.util.Strings;
/**
* This class provides logic for validation of permissions for action parameters which reference node.
@@ -67,15 +68,14 @@ import org.apache.logging.log4j.util.Strings;
public class ActionNodeParameterValidator implements ActionValidator
{
/**
* This list holds action parameter names which require only READ permission on a referenced node
* That means, all other parameters that reference nodes will require WRITE permission
* This list holds action parameter names which require only READ permission on a referenced node That means, all other parameters that reference nodes will require WRITE permission
*/
static final Map<String, List<String>> REQUIRE_READ_PERMISSION_PARAMS =
Map.of(LinkCategoryActionExecuter.NAME, List.of(LinkCategoryActionExecuter.PARAM_CATEGORY_VALUE));
static final Map<String, List<String>> REQUIRE_READ_PERMISSION_PARAMS = Map.of(LinkCategoryActionExecuter.NAME, List.of(LinkCategoryActionExecuter.PARAM_CATEGORY_VALUE));
static final String NO_PROPER_PERMISSIONS_FOR_NODE = "No proper permissions for node: ";
static final String NOT_A_CATEGORY = "Node is not a category ";
static final String NOT_A_FOLDER = "Node is not a folder ";
static final String NO_LONGER_EXISTS = "%s having Id: %s no longer exists. Please update your rule definition.";
private final Actions actions;
private final NamespaceService namespaceService;
@@ -83,7 +83,7 @@ public class ActionNodeParameterValidator implements ActionValidator
private final PermissionService permissionService;
public ActionNodeParameterValidator(Actions actions, NamespaceService namespaceService, Nodes nodes,
PermissionService permissionService)
PermissionService permissionService)
{
this.actions = actions;
this.namespaceService = namespaceService;
@@ -94,7 +94,8 @@ public class ActionNodeParameterValidator implements ActionValidator
/**
* Validates action parameters that reference nodes against access permissions for executing user.
*
* @param action Action to be validated
* @param action
* Action to be validated
*/
@Override
public void validate(Action action)
@@ -124,7 +125,7 @@ public class ActionNodeParameterValidator implements ActionValidator
}
private void validateNodes(final List<ActionDefinition.ParameterDefinition> nodeRefParamDefinitions,
final Action action)
final Action action)
{
if (MapUtils.isNotEmpty(action.getParams()))
{
@@ -132,7 +133,15 @@ public class ActionNodeParameterValidator implements ActionValidator
.filter(pd -> action.getParams().containsKey(pd.getName()))
.forEach(p -> {
final String nodeId = Objects.toString(action.getParams().get(p.getName()), Strings.EMPTY);
final NodeRef nodeRef = nodes.validateNode(nodeId);
NodeRef nodeRef;
try
{
nodeRef = nodes.validateNode(nodeId);
}
catch (EntityNotFoundException e)
{
throw new EntityNotFoundException(String.format(NO_LONGER_EXISTS, p.getDisplayLabel(), nodeId), e);
}
validatePermission(action.getActionDefinitionId(), p.getName(), nodeRef);
validateType(action.getActionDefinitionId(), nodeRef);
});
@@ -163,7 +172,8 @@ public class ActionNodeParameterValidator implements ActionValidator
{
throw new InvalidArgumentException(NOT_A_FOLDER + nodeRef.getId());
}
} else if (!nodes.nodeMatches(nodeRef, Set.of(TYPE_CATEGORY), Collections.emptySet()))
}
else if (!nodes.nodeMatches(nodeRef, Set.of(TYPE_CATEGORY), Collections.emptySet()))
{
throw new InvalidArgumentException(NOT_A_CATEGORY + nodeRef.getId());
}

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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
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=http://docs.alfresco.com/{0}/concepts/ch-administering.html
admin-console.help-link=https://support.hyland.com/p/alfresco
admin-console.success=\u5df2\u6210\u529f\u4fdd\u5b58\u7684\u503c\u3002
admin-console.host=\u4e3b\u673a

View File

@@ -5,4 +5,4 @@
<authentication>guest</authentication>
<transaction allow="readonly">required</transaction>
<lifecycle>internal</lifecycle>
</webscript>
</webscript>

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 = "community" />
<#assign docsEdition = "/Alfresco-Content-Services-Community-Edition/" + server.getVersionMajor() + "." + server.getVersionMinor() + "/Alfresco-Content-Services-Community-Edition" />
<#elseif server.edition == "Enterprise" >
<#assign docsEdition = server.getVersionMajor() + "." + server.getVersionMinor() />
<#assign docsEdition = "/Alfresco-Content-Services/" + server.getVersionMajor() + "." + server.getVersionMinor() + "/Alfresco-Content-Services" />
</#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

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

@@ -214,9 +214,13 @@
<property name="authenticationListener" ref="webScriptAuthenticationListener"/>
<property name="remoteUserMapper" ref="RemoteUserMapper" />
<property name="adminConsoleAuthenticator" ref="AdminConsoleAuthenticator" />
<property name="webScriptsHomeAuthenticator" ref="WebScriptsHomeAuthenticator" />
<property name="alwaysAllowBasicAuthForAdminConsole">
<value>${authentication.alwaysAllowBasicAuthForAdminConsole.enabled}</value>
</property>
<property name="alwaysAllowBasicAuthForWebScriptsHome">
<value>${authentication.alwaysAllowBasicAuthForWebScriptsHome.enabled}</value>
</property>
<property name="getRemoteUserTimeoutMilliseconds">
<value>${authentication.getRemoteUserTimeoutMilliseconds}</value>
</property>

View File

@@ -25,6 +25,20 @@
*/
package org.alfresco.rest.api.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.rest.AbstractSingleNetworkSiteTest;
import org.alfresco.rest.api.Queries;
@@ -36,27 +50,12 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.util.testing.category.LuceneTests;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* V1 REST API tests for pre-defined 'live' search Queries on Sites
* V1 REST API tests for pre-defined 'live' search Queries on Sites
*
* <ul>
* <li> {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/queries/sites} </li>
* <li>{@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/queries/sites}</li>
* </ul>
*
* @author janv
@@ -64,7 +63,7 @@ import static org.mockito.Mockito.verify;
public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
{
private static final String URL_QUERIES_LSS = "queries/sites";
private SiteService siteService;
@Before
@@ -72,7 +71,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
public void setup() throws Exception
{
super.setup();
siteService = (SiteService)applicationContext.getBean("SiteService");
siteService = (SiteService) applicationContext.getBean("SiteService");
}
// Note expectedIds defaults to ids
@@ -86,7 +85,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
}
dummySearchServiceQueryNodeRefs.clear();
for (String id: ids)
for (String id : ids)
{
NodeRef nodeRef = getNodeRef(id);
dummySearchServiceQueryNodeRefs.add(nodeRef);
@@ -98,7 +97,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
if (expectedStatus == 200)
{
String termWithEscapedAsterisks = term.replaceAll("\\*", "\\\\*");
String expectedQuery = "TYPE:\"{http://www.alfresco.org/model/site/1.0}site\" AND (\"*"+ termWithEscapedAsterisks +"*\")";
String expectedQuery = "TYPE:\"{http://www.alfresco.org/model/site/1.0}site\" AND (\"*" + termWithEscapedAsterisks + "*\")";
ArgumentCaptor<SearchParameters> searchParametersCaptor = ArgumentCaptor.forClass(SearchParameters.class);
verify(mockSearchService, times(++callCountToMockSearchService)).query(searchParametersCaptor.capture());
SearchParameters parameters = searchParametersCaptor.getValue();
@@ -109,30 +108,32 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
if (orderBy != null)
{
for (int i=0; i<expectedIds.length; i++)
for (int i = 0; i < expectedIds.length; i++)
{
String id = expectedIds[i];
String actualId = sites.get(i).getId();
assertEquals("Order "+i+":", id, actualId);
assertEquals("Order " + i + ":", id, actualId);
}
}
}
}
/**
/**
* Tests basic api for nodes live search sites - metadata (id, title, description)
*
* <p>GET:</p>
* <p>
* GET:
* </p>
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/queries/sites}
*/
@Test
public void testLiveSearchSites() throws Exception
{
setRequestContext(user1);
AuthenticationUtil.setFullyAuthenticatedUser(user1);
int sCount = 5;
assertTrue(sCount > 4); // as relied on by test below
List<String> siteIds = new ArrayList<>(sCount);
try
@@ -149,14 +150,14 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
String siteD = "siteD";
int charValue = siteI.charAt(0);
// create some some sites with site id: ab00001, abc00002, abcd00003, abcde00004, abcdef00005 (and some specific titles and descriptions)
for (int i = 1; i <= sCount; i++)
{
String num = String.format("%05d", i);
charValue = charValue+1;
siteI = siteI + String.valueOf((char)charValue);
charValue = charValue + 1;
siteI = siteI + String.valueOf((char) charValue);
String siteId = siteI + num + RUNID;
String siteTitle = siteT + num + siteT;
@@ -220,7 +221,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
{
// some cleanup
setRequestContext(user1);
for (String siteId : siteIds)
{
deleteSite(siteId, true, 204);
@@ -230,14 +231,20 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
private NodeRef getNodeRef(String createdSiteId)
{
AuthenticationUtil.setFullyAuthenticatedUser(user1);
// Created sites do not return NodeRefs to the caller so we need to get the NodeRef from the siteService.
// Temporarily as admin we will get NodeRefs to handle ACL authorization.
String userUnderTest = AuthenticationUtil.getFullyAuthenticatedUser();
AuthenticationUtil.setFullyAuthenticatedUser(DEFAULT_ADMIN);
// The following call to siteService.getSite(createdSiteId).getNodeRef() returns a NodeRef like:
// workspace://SpacesStore/9db76769-96de-4de4-bdb4-a127130af362
// workspace://SpacesStore/9db76769-96de-4de4-bdb4-a127130af362
// We call tenantService.getName(nodeRef) to get a fully qualified NodeRef as Solr returns this.
// They look like:
// workspace://@org.alfresco.rest.api.tests.queriespeopleapitest@SpacesStore/9db76769-96de-4de4-bdb4-a127130af362
// workspace://@org.alfresco.rest.api.tests.queriespeopleapitest@SpacesStore/9db76769-96de-4de4-bdb4-a127130af362
NodeRef nodeRef = siteService.getSite(createdSiteId).getNodeRef();
nodeRef = tenantService.getName(nodeRef);
AuthenticationUtil.setFullyAuthenticatedUser(userUnderTest);
return nodeRef;
}
@@ -245,7 +252,7 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
public void testLiveSearchSites_SortPage() throws Exception
{
setRequestContext(user1);
AuthenticationUtil.setFullyAuthenticatedUser(user1);
List<String> siteIds = new ArrayList<>(5);
try
@@ -253,14 +260,14 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
// As user 1 ...
Paging paging = getPaging(0, 100);
// create site
String s1 = createSite("siABCDEF"+RUNID, "ABCDEF DEF", "sdABCDEF", SiteVisibility.PRIVATE, 201).getId();
String s2 = createSite("siABCD"+RUNID, "ABCD DEF", "sdABCD", SiteVisibility.PRIVATE, 201).getId();
String s3 = createSite("siABCDE"+RUNID, "ABCDE DEF", "sdABCDE", SiteVisibility.PRIVATE, 201).getId();
String s4 = createSite("siAB"+RUNID, "AB DEF", "sdAB", SiteVisibility.PRIVATE, 201).getId();
String s5 = createSite("siABC"+RUNID, "ABC DEF", "sdABC", SiteVisibility.PRIVATE, 201).getId();
String s1 = createSite("siABCDEF" + RUNID, "ABCDEF DEF", "sdABCDEF", SiteVisibility.PRIVATE, 201).getId();
String s2 = createSite("siABCD" + RUNID, "ABCD DEF", "sdABCD", SiteVisibility.PRIVATE, 201).getId();
String s3 = createSite("siABCDE" + RUNID, "ABCDE DEF", "sdABCDE", SiteVisibility.PRIVATE, 201).getId();
String s4 = createSite("siAB" + RUNID, "AB DEF", "sdAB", SiteVisibility.PRIVATE, 201).getId();
String s5 = createSite("siABC" + RUNID, "ABC DEF", "sdABC", SiteVisibility.PRIVATE, 201).getId();
// test sort order
{
// default sort order - title asc
@@ -276,11 +283,11 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
// basic paging tests
{
// sort order - title desc
checkApiCall("siAB", "title desc", getPaging(0, 2), 200, new String[] {s1, s3}, s1, s3, s2, s5, s4);
checkApiCall("siAB", "title desc", getPaging(2, 2), 200, new String[] {s2, s5}, s1, s3, s2, s5, s4);
checkApiCall("siAB", "title desc", getPaging(4, 2), 200, new String[] {s4}, s1, s3, s2, s5, s4);
checkApiCall("siAB", "title desc", getPaging(0, 2), 200, new String[]{s1, s3}, s1, s3, s2, s5, s4);
checkApiCall("siAB", "title desc", getPaging(2, 2), 200, new String[]{s2, s5}, s1, s3, s2, s5, s4);
checkApiCall("siAB", "title desc", getPaging(4, 2), 200, new String[]{s4}, s1, s3, s2, s5, s4);
}
// -ve tests
{
// -ve test - invalid sort field
@@ -304,7 +311,52 @@ public class QueriesSitesApiTest extends AbstractSingleNetworkSiteTest
}
}
}
/**
* If the search service do not support ACL filtering, then the Queries API should handle the response to exclude private sites and potential unauthorized error when building response.
*/
@Test
public void testLiveSearchExcludesPrivateSites() throws Exception
{
String publicSiteId = null;
String privateSiteId = null;
try
{
// given
setRequestContext(null, DEFAULT_ADMIN, DEFAULT_ADMIN_PWD);
createUser("bartender");
publicSiteId = createSite("samePrefixPublicSite", "samePrefixPublicSite", "Visible to all users", SiteVisibility.PUBLIC, 201).getId();
privateSiteId = createSite("samePrefixPrivateSite", "samePrefixPrivateSite", "Hidden from bartender", SiteVisibility.PRIVATE, 201).getId();
String[] searchResults = {publicSiteId, privateSiteId};
String[] expectedSites = {publicSiteId};
// when
setRequestContext(null, "bartender", "password");
AuthenticationUtil.setFullyAuthenticatedUser("bartender");
// then
checkApiCall("samePrefix", null, getPaging(0, 100), 200, expectedSites, searchResults);
}
finally
{
// cleanup
AuthenticationUtil.setFullyAuthenticatedUser(DEFAULT_ADMIN);
setRequestContext(null, DEFAULT_ADMIN, DEFAULT_ADMIN_PWD);
if (publicSiteId != null)
{
deleteSite(publicSiteId, true, 204);
}
if (privateSiteId != null)
{
deleteSite(privateSiteId, true, 204);
}
deleteUser("bartender", null);
}
}
@Override
public String getScope()
{

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: http://docs.alfresco.com/5.2/concepts/auth-intro.html
* 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
* 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: http://docs.alfresco.com/5.1/concepts/versioning.html
* Documentation Link: https://support.hyland.com/r/Alfresco/Alfresco-Content-Services/23.4/Alfresco-Content-Services/Configure/Repository/About-Versioning
* 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>23.5.0.12</version>
<version>23.6.0.19</version>
</parent>
<dependencies>
@@ -85,7 +85,7 @@
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta</artifactId>
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@@ -94,7 +94,6 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
@@ -742,10 +741,6 @@
<artifactId>reflections</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -58,6 +58,7 @@ import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FolderExistsException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
@@ -73,6 +74,7 @@ import org.alfresco.util.TempFileProvider;
*
* @author gavinc
*/
@SuppressWarnings("PMD.PreserveStackTrace")
public class ImporterActionExecuter extends ActionExecuterAbstractBase
{
public static final String NAME = "import";
@@ -366,7 +368,11 @@ public class ImporterActionExecuter extends ActionExecuterAbstractBase
catch (FileExistsException e)
{
// TODO: add failed file info to status message?
throw new AlfrescoRuntimeException("Failed to process ZIP file.", e);
if (e.getType().equalsIgnoreCase("folder"))
{
throw new FolderExistsException(root, file.getName());
}
throw e;
}
}
}

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 - 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
@@ -125,6 +125,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;
@@ -209,6 +212,15 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess
}
/**
* @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()
*/
public void reset()
@@ -614,7 +626,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,23 @@ public class RenditionService2Impl implements RenditionService2, InitializingBea
}
}
}
// Checks if the given transform callback is a text extract transform for content indexing or metadata extract/embed.
private boolean isTextOrMetadataExtractTransform(RenderOrTransformCallBack renderOrTransform)
{
RenditionDefinition2 renditionDefinition = renderOrTransform.getRenditionDefinition();
return renditionDefinition != null && ALLOWED_MIMETYPES.contains(renditionDefinition.getTargetMimetype());
}
private boolean isAsyncAllowed(RenderOrTransformCallBack renderOrTransform)
{
// If enabled is false, all async transforms/renditions must be blocked
if (!enabled)
{
return false;
}
// If thumbnails are disabled, allow only text extract or metadata extract/embed transforms
return thumbnailsEnabled || isTextOrMetadataExtractTransform(renderOrTransform);
}
}

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2021 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
@@ -30,6 +30,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.lookup.EntityLookupCache;
import org.alfresco.repo.domain.node.Node;
@@ -41,8 +44,6 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class NodePermissionAssessor
{
@@ -57,7 +58,7 @@ public class NodePermissionAssessor
private long startTime;
private int maxPermissionChecks;
private long maxPermissionCheckTimeMillis;
private EntityLookupCache<Long, Node, NodeRef> nodesCache;
private NodeService nodeService;
private PermissionService permissionService;
@@ -68,7 +69,7 @@ public class NodePermissionAssessor
this.permissionService = permissionService;
this.nodesCache = nodeCache;
this.nodeService = nodeService;
this.checksPerformed = 0;
this.maxPermissionChecks = Integer.MAX_VALUE;
this.maxPermissionCheckTimeMillis = Long.MAX_VALUE;
@@ -82,12 +83,12 @@ public class NodePermissionAssessor
}
public boolean isIncluded(Node node)
{
{
if (isFirstRecord())
{
this.startTime = System.currentTimeMillis();
}
checksPerformed++;
return isReallyIncluded(node);
}
@@ -107,34 +108,34 @@ public class NodePermissionAssessor
String owner = getOwner(node);
return EqualsHelper.nullSafeEquals(authority.getAuthority(), owner);
}
private String getOwner(Node node)
{
nodesCache.setValue(node.getId(), node);
Set<QName> nodeAspects = nodeService.getAspects(node.getNodeRef());
String userName = null;
if (nodeAspects.contains(ContentModel.ASPECT_AUDITABLE))
{
userName = node.getAuditableProperties().getAuditCreator();
}
else if (nodeAspects.contains(ContentModel.ASPECT_OWNABLE))
if (nodeAspects.contains(ContentModel.ASPECT_OWNABLE))
{
Serializable owner = nodeService.getProperty(node.getNodeRef(), ContentModel.PROP_OWNER);
userName = DefaultTypeConverter.INSTANCE.convert(String.class, owner);
}
else if (nodeAspects.contains(ContentModel.ASPECT_AUDITABLE))
{
userName = node.getAuditableProperties().getAuditCreator();
}
return userName;
}
boolean isReallyIncluded(Node node)
{
if (isNullReading)
{
return false;
}
return isSystemReading ||
return isSystemReading ||
isAdminReading ||
canRead(node.getAclId()) ||
isOwnerReading(node, authority);
@@ -151,7 +152,7 @@ public class NodePermissionAssessor
this.maxPermissionChecks = maxPermissionChecks + 1;
}
}
public boolean shouldQuitChecks()
{
if (checksPerformed >= maxPermissionChecks)
@@ -173,7 +174,7 @@ public class NodePermissionAssessor
{
this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis;
}
protected boolean canRead(Long aclId)
{
Boolean res = aclReadCache.get(aclId);
@@ -184,7 +185,7 @@ public class NodePermissionAssessor
}
return res;
}
protected boolean canCurrentUserRead(Long aclId)
{
// cache resolved ACLs
@@ -195,7 +196,7 @@ public class NodePermissionAssessor
{
if (authorities.contains(auth))
{
return false;
return false;
}
}
@@ -204,10 +205,10 @@ public class NodePermissionAssessor
{
if (authorities.contains(auth))
{
return true;
return true;
}
}
return false;
}
}
}

View File

@@ -30,12 +30,12 @@ import jakarta.servlet.http.HttpServletResponse;
import org.alfresco.repo.management.subsystems.ActivateableBean;
/**
* A default {@link AdminConsoleAuthenticator} implementation. Returns null to request a basic auth challenge.
* A default {@link ExternalUserAuthenticator} implementation. Returns null to request a basic auth challenge.
*/
public class DefaultAdminConsoleAuthenticator implements AdminConsoleAuthenticator, ActivateableBean
public class DefaultAdminConsoleAuthenticator implements ExternalUserAuthenticator, ActivateableBean
{
@Override
public String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response)
public String getUserId(HttpServletRequest request, HttpServletResponse response)
{
return null;
}

View File

@@ -0,0 +1,55 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.external;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.alfresco.repo.management.subsystems.ActivateableBean;
/**
* A default {@link ExternalUserAuthenticator} implementation. Returns null to request a basic auth challenge.
*/
public class DefaultWebScriptsHomeAuthenticator implements ExternalUserAuthenticator, ActivateableBean
{
@Override
public String getUserId(HttpServletRequest request, HttpServletResponse response)
{
return null;
}
@Override
public void requestAuthentication(HttpServletRequest request, HttpServletResponse response)
{
// No implementation
}
@Override
public boolean isActive()
{
return false;
}
}

View File

@@ -29,28 +29,17 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* An interface for objects capable of extracting an externally authenticated user ID from the HTTP Admin Console webscript request.
* An interface for objects capable of extracting an externally authenticated user ID from the HTTP request.
*/
public interface AdminConsoleAuthenticator
public interface ExternalUserAuthenticator
{
/**
* Gets an externally authenticated user ID from the HTTP Admin Console webscript request.
* Gets an externally authenticated user ID from the HTTP request.
*
* @param request
* the request
* @param response
* the response
* @return the user ID or <code>null</code> if the user is unauthenticated
*/
String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response);
String getUserId(HttpServletRequest request, HttpServletResponse response);
/**
* Requests an authentication.
*
* @param request
* the request
* @param response
* the response
*/
/* Sends redirect to external site to initiate the OIDC authorization code flow. */
void requestAuthentication(HttpServletRequest request, HttpServletResponse response);
}

View File

@@ -76,6 +76,18 @@ public class IdentityServiceConfig
private String lastNameAttribute;
private String emailAttribute;
private long jwtClockSkewMs;
private String webScriptsHomeRedirectPath;
private String webScriptsHomeScopes;
public String getWebScriptsHomeRedirectPath()
{
return webScriptsHomeRedirectPath;
}
public void setWebScriptsHomeRedirectPath(String webScriptsHomeRedirectPath)
{
this.webScriptsHomeRedirectPath = webScriptsHomeRedirectPath;
}
/**
*
@@ -359,6 +371,18 @@ public class IdentityServiceConfig
this.adminConsoleScopes = adminConsoleScopes;
}
public Set<String> getWebScriptsHomeScopes()
{
return Stream.of(webScriptsHomeScopes.split(","))
.map(String::trim)
.collect(Collectors.toUnmodifiableSet());
}
public void setWebScriptsHomeScopes(String webScriptsHomeScopes)
{
this.webScriptsHomeScopes = webScriptsHomeScopes;
}
public Set<String> getPasswordGrantScopes()
{
return Stream.of(passwordGrantScopes.split(","))

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

@@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.identityservice.admin;
package org.alfresco.repo.security.authentication.identityservice.authentication;
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant.authorizationCode;
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceMetadataKey.SCOPES_SUPPORTED;
@@ -32,7 +32,6 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -43,16 +42,15 @@ 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;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.web.util.UriComponentsBuilder;
import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator;
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
@@ -60,27 +58,26 @@ import org.alfresco.repo.security.authentication.identityservice.IdentityService
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
/**
* 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
public abstract class AbstractIdentityServiceAuthenticator implements ExternalUserAuthenticator
{
private static final Logger LOGGER = LoggerFactory.getLogger(IdentityServiceAdminConsoleAuthenticator.class);
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIdentityServiceAuthenticator.class);
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
private IdentityServiceConfig identityServiceConfig;
private IdentityServiceFacade identityServiceFacade;
private AdminConsoleAuthenticationCookiesService cookiesService;
private RemoteUserMapper remoteUserMapper;
private boolean isEnabled;
protected IdentityServiceConfig identityServiceConfig;
protected IdentityServiceFacade identityServiceFacade;
protected AdminAuthenticationCookiesService cookiesService;
protected RemoteUserMapper remoteUserMapper;
protected abstract String getConfiguredRedirectPath();
protected abstract Set<String> getConfiguredScopes();
@Override
public String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response)
public String getUserId(HttpServletRequest request, HttpServletResponse response)
{
// Try to extract username from the authorization header
String username = remoteUserMapper.getRemoteUser(request);
if (username != null)
{
@@ -107,16 +104,12 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
return null;
}
return remoteUserMapper.getRemoteUser(decorateBearerHeader(bearerToken, request));
HttpServletRequest wrappedRequest = newRequestWrapper(Map.of("Authorization", "Bearer " + bearerToken), request);
return remoteUserMapper.getRemoteUser(wrappedRequest);
}
@Override
public void requestAuthentication(HttpServletRequest request, HttpServletResponse response)
{
respondWithAuthChallenge(request, response);
}
private void respondWithAuthChallenge(HttpServletRequest request, HttpServletResponse response)
{
try
{
@@ -124,7 +117,8 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
{
LOGGER.debug("Responding with the authentication challenge");
}
response.sendRedirect(getAuthenticationRequest(request));
String authenticationRequest = buildAuthRequestUrl(request);
response.sendRedirect(authenticationRequest);
}
catch (IOException e)
{
@@ -133,84 +127,34 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
}
}
private String retrieveTokenUsingAuthCode(HttpServletRequest request, HttpServletResponse response, String code)
protected String getRedirectUri(String requestURL)
{
String bearerToken = null;
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Retrieving a response using the Authorization Code at the Token Endpoint");
}
try
{
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
authorizationCode(code, request.getRequestURL().toString()));
addCookies(response, accessTokenAuthorization);
bearerToken = accessTokenAuthorization.getAccessToken().getTokenValue();
}
catch (AuthorizationException exception)
{
if (LOGGER.isWarnEnabled())
{
LOGGER.warn(
"Error while trying to retrieve a response using the Authorization Code at the Token Endpoint: {}",
exception.getMessage());
}
}
return bearerToken;
return buildRedirectUri(requestURL, getConfiguredRedirectPath());
}
private String refreshTokenIfNeeded(HttpServletRequest request, HttpServletResponse response, String bearerToken)
{
String refreshToken = cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request);
String authTokenExpiration = cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request);
try
{
if (isAuthTokenExpired(authTokenExpiration))
{
bearerToken = refreshAuthToken(refreshToken, response);
}
}
catch (Exception e)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Error while trying to refresh Auth Token: {}", e.getMessage());
}
bearerToken = null;
resetCookies(response);
}
return bearerToken;
}
private void addCookies(HttpServletResponse response, AccessTokenAuthorization accessTokenAuthorization)
{
cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken().getTokenValue(), response);
cookiesService.addCookie(ALFRESCO_TOKEN_EXPIRATION, String.valueOf(
accessTokenAuthorization.getAccessToken().getExpiresAt().toEpochMilli()), response);
cookiesService.addCookie(ALFRESCO_REFRESH_TOKEN, accessTokenAuthorization.getRefreshTokenValue(), response);
}
private String getAuthenticationRequest(HttpServletRequest request)
public String buildAuthRequestUrl(HttpServletRequest request)
{
ClientRegistration clientRegistration = identityServiceFacade.getClientRegistration();
State state = new State();
UriComponentsBuilder authRequestBuilder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getAuthorizationUri())
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails()
.getAuthorizationUri())
.queryParam("client_id", clientRegistration.getClientId())
.queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString()))
.queryParam("response_type", "code")
.queryParam("scope", String.join("+", getScopes(clientRegistration)))
.queryParam("scope", String.join("+", getConfiguredScopes(clientRegistration)))
.queryParam("state", state.toString());
if (StringUtils.isNotBlank(identityServiceConfig.getAudience()))
{
authRequestBuilder.queryParam("audience", identityServiceConfig.getAudience());
builder.queryParam("audience", identityServiceConfig.getAudience());
}
return authRequestBuilder.build().toUriString();
return builder.build()
.toUriString();
}
private Set<String> getScopes(ClientRegistration clientRegistration)
private Set<String> getConfiguredScopes(ClientRegistration clientRegistration)
{
return Optional.ofNullable(clientRegistration.getProviderDetails())
.map(ProviderDetails::getConfigurationMetadata)
@@ -223,100 +167,149 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
private Set<String> getSupportedScopes(Scope scopes)
{
Set<String> configuredScopes = getConfiguredScopes();
return scopes.stream()
.filter(this::hasAdminConsoleScope)
.map(Identifier::getValue)
.filter(configuredScopes::contains)
.collect(Collectors.toSet());
}
private boolean hasAdminConsoleScope(Scope.Value scope)
{
return identityServiceConfig.getAdminConsoleScopes().contains(scope.getValue());
}
private String getRedirectUri(String requestURL)
protected String buildRedirectUri(String requestURL, String overridePath)
{
try
{
URI originalUri = new URI(requestURL);
URI redirectUri = new URI(originalUri.getScheme(), originalUri.getAuthority(), identityServiceConfig.getAdminConsoleRedirectPath(), originalUri.getQuery(), originalUri.getFragment());
String path = overridePath != null ? overridePath : originalUri.getPath();
URI redirectUri = new URI(
originalUri.getScheme(),
originalUri.getAuthority(),
path,
originalUri.getQuery(),
originalUri.getFragment());
return redirectUri.toASCIIString();
}
catch (URISyntaxException e)
{
LOGGER.error("Error while trying to get the redirect URI and respond with the authentication challenge: {}", e.getMessage(), e);
LOGGER.error("Redirect URI construction failed: {}", e.getMessage(), e);
throw new AuthenticationException(e.getMessage(), e);
}
}
private void resetCookies(HttpServletResponse response)
public void challenge(HttpServletRequest request, HttpServletResponse response)
{
try
{
response.sendRedirect(buildAuthRequestUrl(request));
}
catch (IOException e)
{
throw new AuthenticationException("Auth redirect failed", e);
}
}
protected String retrieveTokenUsingAuthCode(HttpServletRequest request, HttpServletResponse response, String code)
{
try
{
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(authorizationCode(code, getRedirectUri(request.getRequestURL()
.toString())));
addCookies(response, accessTokenAuthorization);
return accessTokenAuthorization.getAccessToken()
.getTokenValue();
}
catch (AuthorizationException exception)
{
LOGGER.warn("Error while trying to retrieve token using Authorization Code: {}", exception.getMessage());
return null;
}
}
protected String refreshTokenIfNeeded(HttpServletRequest request, HttpServletResponse response, String bearerToken)
{
String refreshToken = cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request);
String authTokenExpiration = cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request);
try
{
if (isAuthTokenExpired(authTokenExpiration))
{
bearerToken = refreshAuthToken(refreshToken, response);
}
}
catch (Exception e)
{
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Token refresh failed: {}", e.getMessage());
}
bearerToken = null;
resetCookies(response);
}
return bearerToken;
}
private static boolean isAuthTokenExpired(String authTokenExpiration)
{
return authTokenExpiration == null || Instant.now()
.compareTo(Instant.ofEpochMilli(Long.parseLong(authTokenExpiration))) >= 0;
}
private String refreshAuthToken(String refreshToken, HttpServletResponse response)
{
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(AuthorizationGrant.refreshToken(refreshToken));
if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null)
{
throw new AuthenticationException("Refresh token response is invalid.");
}
addCookies(response, accessTokenAuthorization);
return accessTokenAuthorization.getAccessToken()
.getTokenValue();
}
protected void addCookies(HttpServletResponse response, AccessTokenAuthorization accessTokenAuthorization)
{
cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken()
.getTokenValue(), response);
cookiesService.addCookie(ALFRESCO_TOKEN_EXPIRATION, String.valueOf(accessTokenAuthorization.getAccessToken()
.getExpiresAt()
.toEpochMilli()), response);
cookiesService.addCookie(ALFRESCO_REFRESH_TOKEN, accessTokenAuthorization.getRefreshTokenValue(), response);
}
protected void resetCookies(HttpServletResponse response)
{
cookiesService.resetCookie(ALFRESCO_TOKEN_EXPIRATION, response);
cookiesService.resetCookie(ALFRESCO_ACCESS_TOKEN, response);
cookiesService.resetCookie(ALFRESCO_REFRESH_TOKEN, response);
}
private String refreshAuthToken(String refreshToken, HttpServletResponse response)
protected HttpServletRequest newRequestWrapper(Map<String, String> headers, HttpServletRequest request)
{
AccessTokenAuthorization accessTokenAuthorization = doRefreshAuthToken(refreshToken);
addCookies(response, accessTokenAuthorization);
return accessTokenAuthorization.getAccessToken().getTokenValue();
return new AdditionalHeadersHttpServletRequestWrapper(headers, request);
}
private AccessTokenAuthorization doRefreshAuthToken(String refreshToken)
// Setters
public void setIdentityServiceConfig(IdentityServiceConfig config)
{
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
AuthorizationGrant.refreshToken(refreshToken));
if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null)
{
throw new AuthenticationException("AccessTokenResponse is null or empty");
}
return accessTokenAuthorization;
this.identityServiceConfig = config;
}
private static boolean isAuthTokenExpired(String authTokenExpiration)
public void setIdentityServiceFacade(IdentityServiceFacade facade)
{
return Instant.now().compareTo(Instant.ofEpochMilli(Long.parseLong(authTokenExpiration))) >= 0;
this.identityServiceFacade = facade;
}
private HttpServletRequest decorateBearerHeader(String authToken, HttpServletRequest servletRequest)
public void setCookiesService(AdminAuthenticationCookiesService service)
{
Map<String, String> additionalHeaders = new HashMap<>();
additionalHeaders.put("Authorization", "Bearer " + authToken);
return new AdminConsoleHttpServletRequestWrapper(additionalHeaders, servletRequest);
this.cookiesService = service;
}
public void setIdentityServiceFacade(
IdentityServiceFacade identityServiceFacade)
public void setRemoteUserMapper(RemoteUserMapper mapper)
{
this.identityServiceFacade = identityServiceFacade;
}
public void setRemoteUserMapper(RemoteUserMapper remoteUserMapper)
{
this.remoteUserMapper = remoteUserMapper;
}
public void setCookiesService(
AdminConsoleAuthenticationCookiesService cookiesService)
{
this.cookiesService = cookiesService;
}
public void setIdentityServiceConfig(
IdentityServiceConfig identityServiceConfig)
{
this.identityServiceConfig = identityServiceConfig;
}
@Override
public boolean isActive()
{
return this.isEnabled;
}
public void setActive(boolean isEnabled)
{
this.isEnabled = isEnabled;
this.remoteUserMapper = mapper;
}
}

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.admin;
package org.alfresco.repo.security.authentication.identityservice.authentication;
import static java.util.Arrays.asList;
import static java.util.Collections.enumeration;
@@ -37,18 +37,12 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.alfresco.util.PropertyCheck;
public class AdminConsoleHttpServletRequestWrapper extends HttpServletRequestWrapper
public class AdditionalHeadersHttpServletRequestWrapper extends HttpServletRequestWrapper
{
private final Map<String, String> additionalHeaders;
private final HttpServletRequest wrappedRequest;
/**
* Constructs a request object wrapping the given request.
*
* @param request the request to wrap
* @throws IllegalArgumentException if the request is null
*/
public AdminConsoleHttpServletRequestWrapper(Map<String, String> additionalHeaders, HttpServletRequest request)
public AdditionalHeadersHttpServletRequestWrapper(Map<String, String> additionalHeaders, HttpServletRequest request)
{
super(request);
PropertyCheck.mandatory(this, "additionalHeaders", additionalHeaders);

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.admin;
package org.alfresco.repo.security.authentication.identityservice.authentication;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
@@ -33,12 +33,12 @@ import org.alfresco.repo.admin.SysAdminParams;
/**
* Service to handle Admin Console authentication-related cookies.
*/
public class AdminConsoleAuthenticationCookiesService
public class AdminAuthenticationCookiesService
{
private final SysAdminParams sysAdminParams;
private final int cookieLifetime;
public AdminConsoleAuthenticationCookiesService(SysAdminParams sysAdminParams, int cookieLifetime)
public AdminAuthenticationCookiesService(SysAdminParams sysAdminParams, int cookieLifetime)
{
this.sysAdminParams = sysAdminParams;
this.cookieLifetime = cookieLifetime;

View File

@@ -0,0 +1,64 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.identityservice.authentication.admin;
import java.util.Set;
import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
import org.alfresco.repo.security.authentication.identityservice.authentication.AbstractIdentityServiceAuthenticator;
/**
* An {@link ExternalUserAuthenticator} implementation to extract an externally authenticated user ID or to initiate the OIDC authorization code flow.
*/
public class IdentityServiceAdminConsoleAuthenticator extends AbstractIdentityServiceAuthenticator
implements ExternalUserAuthenticator, ActivateableBean
{
private boolean isEnabled;
@Override
protected Set<String> getConfiguredScopes()
{
return identityServiceConfig.getAdminConsoleScopes();
}
@Override
protected String getConfiguredRedirectPath()
{
return identityServiceConfig.getAdminConsoleRedirectPath();
}
@Override
public boolean isActive()
{
return isEnabled;
}
public void setActive(boolean isEnabled)
{
this.isEnabled = isEnabled;
}
}

View File

@@ -0,0 +1,64 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.identityservice.authentication.webscripts;
import java.util.Set;
import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
import org.alfresco.repo.security.authentication.identityservice.authentication.AbstractIdentityServiceAuthenticator;
/**
* An {@link ExternalUserAuthenticator} implementation to extract an externally authenticated user ID or to initiate the OIDC authorization code flow.
*/
public class IdentityServiceWebScriptsHomeAuthenticator extends AbstractIdentityServiceAuthenticator
implements ExternalUserAuthenticator, ActivateableBean
{
private boolean isEnabled;
@Override
protected String getConfiguredRedirectPath()
{
return identityServiceConfig.getWebScriptsHomeRedirectPath();
}
@Override
protected Set<String> getConfiguredScopes()
{
return identityServiceConfig.getWebScriptsHomeScopes();
}
@Override
public boolean isActive()
{
return this.isEnabled;
}
public void setActive(boolean isEnabled)
{
this.isEnabled = isEnabled;
}
}

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
@@ -28,9 +28,6 @@ package org.alfresco.repo.transaction;
import java.lang.reflect.Method;
import java.util.List;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.transaction.TransactionService;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.transaction.PlatformTransactionManager;
@@ -38,6 +35,10 @@ import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.transaction.TransactionService;
/**
* @author Dmitry Velichkevich
*/
@@ -64,7 +65,7 @@ public class RetryingTransactionInterceptor extends TransactionAspectSupport imp
final TransactionAttribute txnAttr = getTransactionAttributeSource().getTransactionAttribute(
method, invocation.getThis().getClass());
final TransactionManager tm = determineTransactionManager(txnAttr);
final TransactionManager tm = determineTransactionManager(txnAttr, null);
if (tm != null && !(tm instanceof PlatformTransactionManager))
{
@@ -83,8 +84,7 @@ public class RetryingTransactionInterceptor extends TransactionAspectSupport imp
retryingTransactionHelper.setExtraExceptions(extraExceptions);
return retryingTransactionHelper.doInTransaction(
new RetryingTransactionCallback<Object>()
{
new RetryingTransactionCallback<Object>() {
public Object execute()
{
TransactionInfo txInfo = createTransactionIfNecessary(ptm, TransactionAttribute.PROPAGATION_REQUIRES_NEW == txnAttr

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
@@ -23,41 +23,55 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.service.cmr.model;
import org.alfresco.api.AlfrescoPublicApi;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Common exception thrown when an operation fails because of a name clash.
*
* @author Derek Hulley
*/
@AlfrescoPublicApi
public class FileExistsException extends AlfrescoRuntimeException
{
private static final String MESSAGE_ID = "file_folder_service.file_exists_message";
private static final long serialVersionUID = -4133713912784624118L;
private NodeRef parentNodeRef;
private String name;
public FileExistsException(NodeRef parentNodeRef, String name)
{
super(MESSAGE_ID, new Object[] { name });
this.parentNodeRef = parentNodeRef;
this.name = name;
}
public NodeRef getParentNodeRef()
{
return parentNodeRef;
}
public String getName()
{
return name;
}
}
package org.alfresco.service.cmr.model;
import org.alfresco.api.AlfrescoPublicApi;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Common exception thrown when an operation fails because of a name clash.
*
* @author Derek Hulley
*/
@AlfrescoPublicApi
public class FileExistsException extends AlfrescoRuntimeException
{
private static final String MESSAGE_ID = "file_folder_service.file_exists_message";
private static final long serialVersionUID = -4133713912784624118L;
private NodeRef parentNodeRef;
private String name;
private String type;
public FileExistsException(NodeRef parentNodeRef, String name)
{
super(MESSAGE_ID, new Object[]{name});
this.parentNodeRef = parentNodeRef;
this.name = name;
}
public FileExistsException(NodeRef parentNodeRef, String name, String type)
{
super(MESSAGE_ID, new Object[]{name});
this.parentNodeRef = parentNodeRef;
this.name = name;
this.type = type;
}
public NodeRef getParentNodeRef()
{
return parentNodeRef;
}
public String getName()
{
return name;
}
public String getType()
{
return type;
}
}

View File

@@ -0,0 +1,62 @@
/*
* #%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.service.cmr.model;
import org.alfresco.api.AlfrescoPublicApi;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* Common exception thrown when an operation fails because of a name clash of folder.
*
*/
@AlfrescoPublicApi
public class FolderExistsException extends AlfrescoRuntimeException
{
private static final String MESSAGE_ID = "file_folder_service.file_exists_message";
private static final long serialVersionUID = -4133713912784624118L;
private NodeRef parentNodeRef;
private String name;
public FolderExistsException(NodeRef parentNodeRef, String name)
{
super(MESSAGE_ID, new Object[]{name});
this.parentNodeRef = parentNodeRef;
this.name = name;
}
public NodeRef getParentNodeRef()
{
return parentNodeRef;
}
public String getName()
{
return name;
}
}

View File

@@ -135,7 +135,7 @@
</property>
<property name="interfaces">
<list>
<value>org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator</value>
<value>org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator</value>
<value>org.alfresco.repo.management.subsystems.ActivateableBean</value>
</list>
</property>
@@ -144,6 +144,22 @@
</property>
</bean>
<bean id="WebScriptsHomeAuthenticator"
class="org.alfresco.repo.management.subsystems.ChainingSubsystemProxyFactory">
<property name="applicationContextManager">
<ref bean="Authentication" />
</property>
<property name="interfaces">
<list>
<value>org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator</value>
<value>org.alfresco.repo.management.subsystems.ActivateableBean</value>
</list>
</property>
<property name="sourceBeanName">
<value>webScriptsHomeAuthenticator</value>
</property>
</bean>
<!-- Passwords are encoded using MD4 -->
<!-- This is not ideal and only done to be compatible with NTLM -->
<!-- authentication against the default authentication mechanism. -->

Some files were not shown because too many files have changed in this diff Show More