Compare commits

..

80 Commits

Author SHA1 Message Date
Arindam Roy
84b38af65b feature/MNT-24961-Cannot-Use-the-Query-Accelerator-with-Search-Enterprise-3 2025-06-04 15:12:20 +05:30
alfresco-build
f1862c9636 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-04 08:13:48 +00:00
alfresco-build
231075fd5e [maven-release-plugin][skip ci] prepare release 25.2.0.41 2025-06-04 08:13:46 +00:00
jakubkochman
979420879c ACS-9646 removed extra space that broke the escaping logic (#3374) 2025-06-04 09:21:31 +02:00
alfresco-build
db330e28f5 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-03 05:02:09 +00:00
alfresco-build
bae0573636 [maven-release-plugin][skip ci] prepare release 25.2.0.40 2025-06-03 05:02:07 +00:00
SatyamSah5
8089fc2572 [ACS-9697] Added user-friendly error message. (#3371) 2025-06-03 09:41:57 +05:30
alfresco-build
1c4fe53c0f [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-01 00:09:36 +00:00
alfresco-build
7a8aa1a2c1 [maven-release-plugin][skip ci] prepare release 25.2.0.39 2025-06-01 00:09:34 +00:00
Alfresco CI User
e08ba1fd4f [force] Force release for 2025-06-01. 2025-06-01 00:05:59 +00:00
alfresco-build
4f40bd0687 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-29 09:34:20 +00:00
alfresco-build
a3578f7baa [maven-release-plugin][skip ci] prepare release 25.2.0.38 2025-05-29 09:34:18 +00:00
jakubkochman
8e8b9c868f ACS-9635 bumped httpclient5 to 5.5 to fix CVE-2025-27820(#3369) 2025-05-29 10:53:01 +02:00
cezary-witkowski
f77b3b79e5 [MNT-24859] Basic Auth still possible with Keycloak enabled (#3361)
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
Co-authored-by: Sathish Kumar <ST28@ford.com>
Co-authored-by: pmm <purusothaman.mm@hyland.com>
Co-authored-by: purusothaman-mm <purusothman.mm@hyland.com>
2025-05-27 13:31:00 +02:00
alfresco-build
3a7157f4a7 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-26 16:46:11 +00:00
alfresco-build
d6b979f341 [maven-release-plugin][skip ci] prepare release 25.2.0.37 2025-05-26 16:46:09 +00:00
varapathijanakiram
a090de4e71 Merge pull request #3367 from Alfresco/revert-3333-fix/MNT-24776
Revert "Fix category picker visibility to show only permitted categories based on local permissions"
2025-05-26 21:33:31 +05:30
varapathijanakiram
03621db30a Revert "Fix category picker visibility to show only permitted categories base…"
This reverts commit 8645cdc76d.
2025-05-26 20:45:15 +05:30
alfresco-build
766a6def2b [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-25 00:08:54 +00:00
alfresco-build
117804fb68 [maven-release-plugin][skip ci] prepare release 25.2.0.36 2025-05-25 00:08:52 +00:00
Alfresco CI User
f03e6761ce [force] Force release for 2025-05-25. 2025-05-25 00:05:09 +00:00
alfresco-build
74c8288206 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-23 12:33:45 +00:00
alfresco-build
c0bd0a680b [maven-release-plugin][skip ci] prepare release 25.2.0.35 2025-05-23 12:33:42 +00:00
varapathijanakiram
8645cdc76d Fix category picker visibility to show only permitted categories based on local permissions (#3333) 2025-05-23 17:19:34 +05:30
alfresco-build
5055eec2df [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-23 07:27:34 +00:00
alfresco-build
892f41d6fd [maven-release-plugin][skip ci] prepare release 25.2.0.34 2025-05-23 07:27:32 +00:00
Damian Ujma
075b02baee ACS-9429 Fix AGS Roles API (#3365) 2025-05-23 08:34:27 +02:00
alfresco-build
7c8a75ce6c [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-19 07:21:46 +00:00
alfresco-build
cb0a925e27 [maven-release-plugin][skip ci] prepare release 25.2.0.33 2025-05-19 07:21:44 +00:00
Damian Ujma
c7d2699f7e ACS-9428 Add AGS Roles V1 API (#3287) 2025-05-19 08:27:46 +02:00
alfresco-build
b942b55193 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-19 05:30:08 +00:00
alfresco-build
c1100fe983 [maven-release-plugin][skip ci] prepare release 25.2.0.32 2025-05-19 05:30:06 +00:00
SatyamSah5
62236c90f5 [ACS-9572] show proper message for duplicate unzipping (#3348) 2025-05-19 10:02:53 +05:30
alfresco-build
eabdab91fb [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-18 00:08:45 +00:00
alfresco-build
4f83076cfe [maven-release-plugin][skip ci] prepare release 25.2.0.31 2025-05-18 00:08:43 +00:00
Alfresco CI User
7eda1d420f [force] Force release for 2025-05-18. 2025-05-18 00:05:03 +00:00
alfresco-build
411388d62d [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-16 17:47:37 +00:00
alfresco-build
245d1317ac [maven-release-plugin][skip ci] prepare release 25.2.0.30 2025-05-16 17:47:35 +00:00
Damian Ujma
ee5e34ca32 ACS-9399 Add AGS Roles V1 API (Read) (#3350)
Co-authored-by: SatyamSah5 <satyam.sah25@rediffmail.com>
Co-authored-by: bsayan2 <sayan.bhattacharya@hyland.com>
2025-05-16 18:55:45 +02:00
alfresco-build
c4dcef73e1 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-16 15:15:16 +00:00
alfresco-build
4f53fee1fc [maven-release-plugin][skip ci] prepare release 25.2.0.29 2025-05-16 15:15:13 +00:00
Gerard Olenski
d163410e3d ACS-9578 Improve stability in AddToHoldsBulkV1Tests (#3355) 2025-05-16 16:30:55 +02:00
jakubkochman
9ca251edba Feature/acs 9456 SCIM user sync (#3324) 2025-05-15 10:55:09 +02:00
alfresco-build
193cb9b30d [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-15 08:07:08 +00:00
alfresco-build
e9a36f67fe [maven-release-plugin][skip ci] prepare release 25.2.0.28 2025-05-15 08:07:06 +00:00
DurgDineshsai
c18a58caea Merge pull request #3338 from Alfresco/fix/MNT-24146
[MNT-24146] - Unable to update password for 'admin' user
2025-05-15 12:00:59 +05:30
alfresco-build
11659ab917 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-14 14:10:25 +00:00
alfresco-build
89b1049809 [maven-release-plugin][skip ci] prepare release 25.2.0.27 2025-05-14 14:10:23 +00:00
Belal Ansari
192c105719 ACS-9578 Changes done as part of test case failure for AddToHoldsBulkV1Tests (#3345) 2025-05-14 18:58:41 +05:30
DurgDineshsai
b8fc8efa07 [MNT-24146] Removing the unnecessary logic 2025-05-13 12:42:58 +05:30
DurgDineshsai
2e851cf88d [MNT-24146] Admin user unable to update passowrd 2025-05-13 10:39:16 +05:30
alfresco-build
ebf081c731 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-11 00:08:29 +00:00
alfresco-build
b979701264 [maven-release-plugin][skip ci] prepare release 25.2.0.26 2025-05-11 00:08:26 +00:00
Alfresco CI User
aa0d02abf2 [force] Force release for 2025-05-11. 2025-05-11 00:04:50 +00:00
alfresco-build
2f7b8d50a3 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-09 09:53:33 +00:00
alfresco-build
800736a025 [maven-release-plugin][skip ci] prepare release 25.2.0.25 2025-05-09 09:53:31 +00:00
Kacper Magdziarz
b8b7e5193e [ACS-9635] Revert updated of HttpClient5 and HttpCore5 (#3337) 2025-05-09 11:01:35 +02:00
alfresco-build
808faa71b3 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-08 13:20:15 +00:00
alfresco-build
0bd476968b [maven-release-plugin][skip ci] prepare release 25.2.0.24 2025-05-08 13:20:12 +00:00
Kacper Magdziarz
e51e5e8ca5 [PRODSEC-10123] Bump Httpcore5 to 5.3.4 2025-05-08 14:34:42 +02:00
alfresco-build
46db14d5ff [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-07 17:09:56 +00:00
alfresco-build
b5fa73ca3b [maven-release-plugin][skip ci] prepare release 25.2.0.23 2025-05-07 17:09:54 +00:00
Kacper Magdziarz
c962daae3b [PRODSEC-10123] Bump Httpclient5 to 5.4.4 (#3336) 2025-05-07 18:18:26 +02:00
alfresco-build
8efc559b09 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-05 07:30:49 +00:00
alfresco-build
08628732fc [maven-release-plugin][skip ci] prepare release 25.2.0.22 2025-05-05 07:30:47 +00:00
tathagta15
7f74bf7b3e Bump camel version to 4.11.0 (#3334) 2025-05-05 12:08:42 +05:30
alfresco-build
390073b153 [maven-release-plugin][skip ci] prepare for next development iteration 2025-05-04 00:08:55 +00:00
alfresco-build
68c87f69c5 [maven-release-plugin][skip ci] prepare release 25.2.0.21 2025-05-04 00:08:53 +00:00
Alfresco CI User
2b936050c8 [force] Force release for 2025-05-04. 2025-05-04 00:05:08 +00:00
alfresco-build
28184ca69a [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-30 06:54:38 +00:00
alfresco-build
e05c74813e [maven-release-plugin][skip ci] prepare release 25.2.0.20 2025-04-30 06:54:36 +00:00
tathagta15
e05a1d9ba9 Bump spring-security to 6.3.8 (#3329) 2025-04-30 11:14:56 +05:30
tathagta15
5aab15a77a Bump poi-ooxml version to 5.4.0 (#3328) 2025-04-29 18:10:26 +05:30
alfresco-build
5d267c8d60 [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-27 00:08:37 +00:00
alfresco-build
18fc9a58b4 [maven-release-plugin][skip ci] prepare release 25.2.0.19 2025-04-27 00:08:34 +00:00
Alfresco CI User
6a7ba876b7 [force] Force release for 2025-04-27. 2025-04-27 00:04:58 +00:00
alfresco-build
65bdb242ec [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-25 08:09:22 +00:00
alfresco-build
5a537b301a [maven-release-plugin][skip ci] prepare release 25.2.0.18 2025-04-25 08:09:20 +00:00
KushalBanik
515b894241 [MNT-24490] Reference for AlfrescoSQLServerDialect changed to SQLServerDialect (#3323) 2025-04-25 12:54:39 +05:30
alfresco-build
4978d9e790 [maven-release-plugin][skip ci] prepare for next development iteration 2025-04-23 07:29:04 +00:00
92 changed files with 2499 additions and 348 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
}
],
@@ -1868,5 +1868,5 @@
}
]
},
"generated_at": "2025-03-27T23:45:41Z"
"generated_at": "2025-04-22T06:32:47Z"
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.model;
public record CapabilityModel(String name, String title, String description, GroupModel group, int index)
{}

View File

@@ -0,0 +1,30 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.model;
public record GroupModel(String id, String title)
{}

View File

@@ -0,0 +1,91 @@
/*-
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.model.role;
import java.util.List;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.alfresco.rest.rm.community.model.CapabilityModel;
import org.alfresco.utility.model.TestModel;
/**
* POJO for role
*/
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role extends TestModel
{
@JsonProperty(required = true)
private String name;
@JsonProperty(required = true)
private List<CapabilityModel> capabilities;
@JsonProperty(required = true)
private String displayLabel;
@JsonProperty(required = true)
private String groupShortName;
private List<String> assignedUsers;
private List<String> assignedGroups;
private String roleGroupName;
@Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
Role role = (Role) o;
return Objects.equals(name, role.name) && Objects.equals(capabilities, role.capabilities)
&& Objects.equals(displayLabel, role.displayLabel) && Objects.equals(groupShortName, role.groupShortName) && Objects.equals(assignedUsers, role.assignedUsers)
&& Objects.equals(assignedGroups, role.assignedGroups) && Objects.equals(roleGroupName, role.roleGroupName);
}
@Override
public int hashCode()
{
return Objects.hash(name, capabilities, displayLabel, groupShortName, assignedUsers, assignedGroups, roleGroupName);
}
}

View File

@@ -0,0 +1,32 @@
/*-
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.model.role;
import org.alfresco.rest.core.RestModels;
public class RoleCollection extends RestModels<RoleEntry, RoleCollection>
{}

View File

@@ -0,0 +1,47 @@
/*-
* #%L
* Alfresco Records Management Module
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* -
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
* -
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* -
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
* -
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.rm.community.model.role;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.alfresco.rest.core.RestModels;
@Builder
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class RoleEntry extends RestModels<Role, RoleEntry>
{
@JsonProperty
private Role entry;
}

View File

@@ -35,7 +35,7 @@ package org.alfresco.rest.rm.community.model.user;
*/
public enum UserRoles
{
IN_PLACE_WRITERS("ExtendedWriters", "In-Place Writers"), ROLE_RM_ADMIN("Administrator", "Records Management Administrator"), ROLE_RM_MANAGER("RecordsManager", "Records Management Manager"), ROLE_RM_POWER_USER("PowerUser", "Records Management Power User"), ROLE_RM_SECURITY_OFFICER("SecurityOfficer", "Records Management Security Officer"), ROLE_RM_USER("User", "Records Management User");
IN_PLACE_WRITERS("ExtendedWriters", "In-Place Writers"), ROLE_RM_ADMIN("Administrator", "Records Management Administrator"), ROLE_RM_MANAGER("RecordsManager", "Records Management Manager"), ROLE_RM_POWER_USER("PowerUser", "Records Management Power User"), ROLE_RM_SECURITY_OFFICER("SecurityOfficer", "Records Management Security Officer"), ROLE_RM_USER("User", "Records Management User"), IN_PLACE_READERS("ExtendedReaders", "In-Place Readers");
public final String roleId;
public final String displayName;

View File

@@ -43,6 +43,7 @@ import org.alfresco.rest.rm.community.model.hold.Hold;
import org.alfresco.rest.rm.community.model.hold.HoldCollection;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryCollection;
import org.alfresco.rest.rm.community.model.role.RoleCollection;
import org.alfresco.rest.rm.community.requests.RMModelRequest;
/**
@@ -303,4 +304,39 @@ public class FilePlanAPI extends RMModelRequest
{
return getHolds(filePlanId, EMPTY);
}
/**
* Gets the roles of a file plan.
*
* @param filePlanId
* The identifier of a file plan
* @param parameters
* The URL parameters to add
* @return The {Pagination and RoleModel Entries} for the given {@code filePlanId}
* @throws RuntimeException
* for the following cases:
* <ul>
* <li>authentication fails</li>
* <li>current user does not have permission to read {@code filePlanId}</li>
* <li>{@code filePlanId} does not exist</li>
* </ul>
*/
public RoleCollection getFilePlanRoles(String filePlanId, String parameters)
{
mandatoryString("filePlanId", filePlanId);
return getRmRestWrapper().processModels(RoleCollection.class, simpleRequest(
GET,
"file-plans/{filePlanId}/roles?{parameters}",
filePlanId,
parameters));
}
/**
* See {@link #getFilePlanRoles(String, String)}
*/
public RoleCollection getFilePlanRoles(String filePlanId)
{
return getFilePlanRoles(filePlanId, EMPTY);
}
}

View File

@@ -93,6 +93,7 @@ import org.alfresco.rest.rm.community.requests.gscore.api.RecordsAPI;
import org.alfresco.rest.search.RestRequestQueryModel;
import org.alfresco.rest.search.SearchNodeModel;
import org.alfresco.rest.search.SearchRequest;
import org.alfresco.rest.v0.RMRolesAndActionsAPI;
import org.alfresco.rest.v0.SearchAPI;
import org.alfresco.utility.Utility;
import org.alfresco.utility.data.DataUserAIS;
@@ -127,6 +128,10 @@ public class BaseRMRestTest extends RestTest
@Getter(value = PROTECTED)
private SearchAPI searchApi;
@Autowired
@Getter(PROTECTED)
private RMRolesAndActionsAPI rmRolesAndActionsV0API;
protected static final String iso8601_DateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
/**

View File

@@ -28,6 +28,7 @@ package org.alfresco.rest.rm.community.fileplans;
import static java.util.Arrays.asList;
import static com.google.common.collect.Sets.newHashSet;
import static org.springframework.http.HttpStatus.CONFLICT;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.FORBIDDEN;
@@ -56,19 +57,27 @@ import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanCo
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_CONTAINER_TYPE;
import static org.alfresco.rest.rm.community.model.fileplancomponents.FilePlanComponentType.UNFILED_RECORD_FOLDER_TYPE;
import static org.alfresco.rest.rm.community.model.user.UserPermissions.PERMISSION_FILING;
import static org.alfresco.rest.rm.community.model.user.UserRoles.IN_PLACE_READERS;
import static org.alfresco.rest.rm.community.model.user.UserRoles.IN_PLACE_WRITERS;
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_ADMIN;
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_MANAGER;
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_POWER_USER;
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_SECURITY_OFFICER;
import static org.alfresco.rest.rm.community.model.user.UserRoles.ROLE_RM_USER;
import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric;
import static org.alfresco.utility.data.RandomData.getRandomName;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
import org.alfresco.rest.rm.community.base.DataProviderClass;
import org.alfresco.rest.rm.community.model.CapabilityModel;
import org.alfresco.rest.rm.community.model.fileplan.FilePlan;
import org.alfresco.rest.rm.community.model.fileplan.FilePlanProperties;
import org.alfresco.rest.rm.community.model.hold.Hold;
@@ -76,6 +85,9 @@ import org.alfresco.rest.rm.community.model.hold.HoldCollection;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryCollection;
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategoryProperties;
import org.alfresco.rest.rm.community.model.role.Role;
import org.alfresco.rest.rm.community.model.role.RoleCollection;
import org.alfresco.rest.rm.community.model.user.UserCapabilities;
import org.alfresco.rest.rm.community.requests.gscore.api.RMSiteAPI;
import org.alfresco.utility.constants.ContainerName;
import org.alfresco.utility.model.UserModel;
@@ -87,6 +99,7 @@ import org.alfresco.utility.report.Bug;
* @author Rodica Sutu
* @since 2.6
*/
@SuppressWarnings("PMD.UnitTestShouldIncludeAssert")
public class FilePlanTests extends BaseRMRestTest
{
// ** Number of children (for children creation test) */
@@ -266,7 +279,7 @@ public class FilePlanTests extends BaseRMRestTest
* When I ask the API to create a root record category
* Then it is created as a root record category
* </pre>
*
*
* <pre>
* Given that a file plan exists
* When I use the API to create a folder (cm:folder type) into the fileplan
@@ -314,7 +327,7 @@ public class FilePlanTests extends BaseRMRestTest
* When I ask the API to create a root category having the same name
* Then the response code received is 409 - name clashes with an existing node
* </pre>
*
*
* <pre>
* Given a root category
* When I ask the API to create a root category having the same name with autoRename parameter on true
@@ -594,4 +607,171 @@ public class FilePlanTests extends BaseRMRestTest
}
});
}
/**
* <pre>
* Given that a file plan exists
* When rmAdmin user ask the API for roles
* It provides list of all default roles
* </pre>
*/
@Test
public void listFilePlanAllDefaultRoles()
{
List<String> defaultRolesDisplayNames = asList(IN_PLACE_READERS.displayName, ROLE_RM_ADMIN.displayName, ROLE_RM_MANAGER.displayName, ROLE_RM_POWER_USER.displayName, ROLE_RM_USER.displayName, IN_PLACE_WRITERS.displayName, ROLE_RM_SECURITY_OFFICER.displayName);
// Call to new API to get the roles and capabilities
RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI().getFilePlanRoles(FILE_PLAN_ALIAS);
assertStatusCode(OK);
roleCollection.getEntries().forEach(roleModelEntry -> {
Role role = roleModelEntry.getEntry();
assertTrue(defaultRolesDisplayNames.contains(role.getDisplayLabel()));
assertNotNull(role.getCapabilities());
});
}
/**
* <pre>
* Given that a file plan exists
* When rmAdmin user ask the API for roles with SystemRoles as false
* It provides list of all roles excluding SystemRoles
* </pre>
*/
@Test
public void listFilePlanAllRolesExcludeSystemRoles()
{
String parameters = "where=(systemRoles=false)";
List<String> systemRolesDisplayNames = asList(IN_PLACE_WRITERS.displayName, IN_PLACE_READERS.displayName);
// Call to new API to get the roles and capabilities
RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI().getFilePlanRoles(FILE_PLAN_ALIAS, parameters);
assertStatusCode(OK);
roleCollection.getEntries().forEach(roleModelEntry -> {
Role role = roleModelEntry.getEntry();
assertFalse(systemRolesDisplayNames.contains(role.getDisplayLabel()));
assertNotNull(role.getCapabilities());
});
}
/**
* <pre>
* Given that a file plan exists
* When a non-RM user asks the API for the roles
* Then the status code 403 (Permission denied) is return
* </pre>
*/
@Test
public void nonRmUserFilePlanRoles()
{
// Create a random user
UserModel nonRMuser = getDataUser().createRandomTestUser("testUser");
// Call to new API to get the roles and capabilities
getRestAPIFactory().getFilePlansAPI(nonRMuser).getFilePlanRoles(FILE_PLAN_ALIAS);
assertStatusCode(FORBIDDEN);
}
/**
* <pre>
* Given that a file plan exists
* When a RM_Manager user asks the API for the roles
* returns the RM_Manager role and capabilities
* </pre>
*/
@Test
public void rmManagerFilePlanRolesAndCapabilities()
{
// Create a random user
UserModel managerUser = getDataUser().createRandomTestUser("managerUser");
// Assign RecordsManager role to user
getRestAPIFactory().getRMUserAPI().assignRoleToUser(managerUser.getUsername(), ROLE_RM_MANAGER.roleId);
String parameters = "where=(personId='" + managerUser.getUsername() + "')";
// Call to new API to get the roles and capabilities
RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI(managerUser).getFilePlanRoles(FILE_PLAN_ALIAS, parameters);
roleCollection.getEntries().forEach(roleModelEntry -> {
Role role = roleModelEntry.getEntry();
assertEquals(ROLE_RM_MANAGER.displayName, role.getDisplayLabel());
assertNotNull(role.getCapabilities());
});
}
/**
* <pre>
* Given that a file plan exists
* When a User with more than one role asks the API for the roles and relation
* returns the roles and capabilities
* </pre>
*/
@Test
public void multipleRoleUserFilePlanRolesAndCapabilities()
{
// Create a random user
UserModel rmUser = getDataUser().createRandomTestUser("rmUser");
// Assign rmUser role to user
getRestAPIFactory().getRMUserAPI().assignRoleToUser(rmUser.getUsername(), ROLE_RM_USER.roleId);
getRestAPIFactory().getRMUserAPI().assignRoleToUser(rmUser.getUsername(), ROLE_RM_POWER_USER.roleId);
String parameters = "where=(personId='" + rmUser.getUsername() + "')";
// Call to new API to get the roles and capabilities
RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI(rmUser).getFilePlanRoles(FILE_PLAN_ALIAS, parameters);
assertStatusCode(OK);
assertEquals(roleCollection.getEntries().size(), 2);
roleCollection.getEntries().forEach(roleModelEntry -> {
Role role = roleModelEntry.getEntry();
assertTrue(role.getDisplayLabel().equals(ROLE_RM_USER.displayName) || role.getDisplayLabel().equals(ROLE_RM_POWER_USER.displayName));
assertNotNull(role.getCapabilities());
});
}
/**
* <pre>
* Given that a file plan exists
* When a new user with a new role asks the API for the roles and relation
* returns the new role and new capabilities
* </pre>
*/
@Test
public void newRoleUserFilePlanRolesAndCapabilities()
{
/** A list of capabilities. */
Set<String> newCapabilities = newHashSet(UserCapabilities.VIEW_RECORDS_CAP, UserCapabilities.DECLARE_RECORDS_CAP);
// Create a new role using old API
getRmRolesAndActionsV0API().createRole(getAdminUser().getUsername(), getAdminUser().getPassword(), "NewTestRole",
"New Role Label", newCapabilities);
// Create a random user
UserModel rmNewUser = getDataUser().createRandomTestUser("rmPowerUser");
// Assign New role to user
getRestAPIFactory().getRMUserAPI().assignRoleToUser(rmNewUser.getUsername(), "NewTestRole");
String parameters = "where=(personId='" + rmNewUser.getUsername() + "')";
// Call to new API to get the roles and capabilities
RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI(rmNewUser).getFilePlanRoles(FILE_PLAN_ALIAS, parameters);
assertStatusCode(OK);
assertEquals(roleCollection.getEntries().size(), 1);
roleCollection.getEntries().forEach(roleModelEntry -> {
List<CapabilityModel> capabilities = roleModelEntry.getEntry().getCapabilities();
capabilities.forEach(capabilityModel -> {
assertTrue(newCapabilities.contains(capabilityModel.name()));
});
});
}
/**
* <pre>
* Given that a file plan exists
* When API call happens with Capability filter
* returns roles associated with the capability
* </pre>
*/
@Test
public void filePlanRolesAndCapabilitiesFilter()
{
String parameters = "where=(systemRoles=true and capabilityName in ('ManageRules'))";
// Call to new API to get the roles and capabilities, filter by capability, include assigned users
RoleCollection roleCollection = getRestAPIFactory().getFilePlansAPI().getFilePlanRoles(FILE_PLAN_ALIAS, parameters);
assertStatusCode(OK);
assertEquals(roleCollection.getEntries().size(), 1);
roleCollection.getEntries().forEach(roleModelEntry -> {
Role role = roleModelEntry.getEntry();
assertEquals(ROLE_RM_ADMIN.displayName, role.getDisplayLabel());
assertNotNull(role.getCapabilities());
});
}
}

View File

@@ -134,6 +134,16 @@ public class AddToHoldsBulkV1Tests extends BaseRMRestTest
.until(() -> getRestAPIFactory().getSearchAPI(null).search(searchRequest).getPagination()
.getTotalItems() == NUMBER_OF_FILES);
RestRequestQueryModel ancestorReq = getContentFromFolderAndAllSubfoldersQuery(rootFolder.getNodeRefWithoutVersion());
SearchRequest ancestorSearchRequest = new SearchRequest();
ancestorSearchRequest.setQuery(ancestorReq);
STEP("Wait until paths are indexed.");
// to improve stability on CI - seems that sometimes during big load we need to wait longer for the condition
await().atMost(120, TimeUnit.SECONDS)
.until(() -> getRestAPIFactory().getSearchAPI(null).search(ancestorSearchRequest).getPagination()
.getTotalItems() == NUMBER_OF_FILES);
holdBulkOperation = HoldBulkOperation.builder()
.query(queryReq)
.op(HoldBulkOperationType.ADD).build();

View File

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

View File

@@ -78,6 +78,12 @@
<property name="transactionService" ref="transactionService" />
</bean>
<bean class="org.alfresco.rm.rest.api.fileplans.FilePlanRolesRelation">
<property name="apiUtils" ref="apiUtils" />
<property name="rmRoles" ref="rm.roles" />
<property name="filePlanService" ref="FilePlanService" />
</bean>
<bean class="org.alfresco.rm.rest.api.holds.HoldsEntityResource" >
<property name="holdService" ref="HoldService" />
<property name="apiUtils" ref="apiUtils" />
@@ -228,6 +234,11 @@
<property name="siteSurfConfig" ref="rm.siteSurfConfig" />
</bean>
<bean id="rm.roles" class="org.alfresco.rm.rest.api.impl.RMRolesImpl">
<property name="nodesModelFactory" ref="nodesModelFactory" />
<property name="filePlanRoleService" ref="FilePlanRoleService"/>
</bean>
<bean id="rm.siteSurfConfig" class="org.alfresco.rest.api.impl.SiteSurfConfig">
<property name="configPath" value="alfresco/module/org_alfresco_module_rm/bootstrap/site"/>
</bean>
@@ -280,4 +291,4 @@
<property name="beanName" value="restJsonModule" />
<property name="extendingBeanName" value="rm.restJsonModule" />
</bean>
</beans>
</beans>

View File

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

View File

@@ -0,0 +1,56 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rm.rest.api.model.RoleModel;
import org.alfresco.service.cmr.repository.NodeRef;
/**
* RM Roles API
*/
public interface RMRoles
{
String PARAM_INCLUDE_ASSIGNED_USERS = "assignedUsers";
String PARAM_INCLUDE_ASSIGNED_GROUPS = "assignedGroups";
String PARAM_INCLUDE_SYSTEM_ROLES = "systemRoles";
String PARAM_CAPABILITY_NAME = "capabilityName";
String PARAM_PERSON_ID = "personId";
/**
* Gets a list of roles.
*
* @param filePlan
* the file plan node reference
* @param parameters
* the {@link Parameters} object to get the parameters passed into the request including: - filter, sort & paging params (where, orderBy, skipCount, maxItems) - include param (personId, includeSystemRoles)
* @return a paged list of {@code org.alfresco.rm.rest.api.model.RoleModel} objects
*/
CollectionWithPagingInfo<RoleModel> getRoles(NodeRef filePlan, Parameters parameters);
}

View File

@@ -0,0 +1,96 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api.fileplans;
import static org.alfresco.util.ParameterCheck.mandatory;
import org.springframework.beans.factory.InitializingBean;
import org.alfresco.module.org_alfresco_module_rm.fileplan.FilePlanService;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rm.rest.api.RMRoles;
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
import org.alfresco.rm.rest.api.model.RoleModel;
import org.alfresco.service.cmr.repository.NodeRef;
@RelationshipResource(name = "roles", entityResource = FilePlanEntityResource.class, title = "Roles in a file plan")
public class FilePlanRolesRelation implements RelationshipResourceAction.Read<RoleModel>, InitializingBean
{
private RMRoles rmRoles;
private FilePlanService filePlanService;
private FilePlanComponentsApiUtils apiUtils;
@Override
public void afterPropertiesSet() throws Exception
{
mandatory("rmRoles", this.rmRoles);
mandatory("apiUtils", this.apiUtils);
mandatory("filePlanService", this.filePlanService);
}
@Override
public CollectionWithPagingInfo<RoleModel> readAll(String filePlanId, Parameters params)
{
NodeRef filePlanNodeRef = getFilePlan(filePlanId);
if (filePlanNodeRef == null)
{
throw new EntityNotFoundException(filePlanId);
}
return rmRoles.getRoles(filePlanNodeRef, params);
}
private NodeRef getFilePlan(String filePlanId)
{
NodeRef filePlanNodeRef = apiUtils.lookupAndValidateNodeType(filePlanId, RecordsManagementModel.TYPE_FILE_PLAN);
if (!FilePlanComponentsApiUtils.FILE_PLAN_ALIAS.equals(filePlanId))
{
filePlanNodeRef = filePlanService.getFilePlan(filePlanNodeRef);
}
return filePlanNodeRef;
}
public void setRmRoles(RMRoles rmRoles)
{
this.rmRoles = rmRoles;
}
public void setApiUtils(FilePlanComponentsApiUtils apiUtils)
{
this.apiUtils = apiUtils;
}
public void setFilePlanService(FilePlanService filePlanService)
{
this.filePlanService = filePlanService;
}
}

View File

@@ -30,6 +30,7 @@ package org.alfresco.rm.rest.api.impl;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -42,12 +43,15 @@ import org.slf4j.LoggerFactory;
import org.alfresco.model.ContentModel;
import org.alfresco.module.org_alfresco_module_rm.RecordsManagementServiceRegistry;
import org.alfresco.module.org_alfresco_module_rm.capability.Capability;
import org.alfresco.module.org_alfresco_module_rm.capability.Group;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinitionImpl;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
import org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEvent;
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
import org.alfresco.module.org_alfresco_module_rm.role.Role;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.AssocChild;
import org.alfresco.rest.api.model.ContentInfo;
@@ -55,7 +59,9 @@ import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.api.model.UserInfo;
import org.alfresco.rest.framework.jacksonextensions.BeanPropertiesFilter;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rm.rest.api.model.CapabilityModel;
import org.alfresco.rm.rest.api.model.FilePlan;
import org.alfresco.rm.rest.api.model.GroupModel;
import org.alfresco.rm.rest.api.model.HoldModel;
import org.alfresco.rm.rest.api.model.RMNode;
import org.alfresco.rm.rest.api.model.Record;
@@ -66,6 +72,7 @@ import org.alfresco.rm.rest.api.model.RetentionPeriod;
import org.alfresco.rm.rest.api.model.RetentionSchedule;
import org.alfresco.rm.rest.api.model.RetentionScheduleActionDefinition;
import org.alfresco.rm.rest.api.model.RetentionSteps;
import org.alfresco.rm.rest.api.model.RoleModel;
import org.alfresco.rm.rest.api.model.Transfer;
import org.alfresco.rm.rest.api.model.TransferChild;
import org.alfresco.rm.rest.api.model.TransferContainer;
@@ -696,6 +703,36 @@ public class ApiNodesModelFactory
(String) info.getProperties().get(RecordsManagementModel.PROP_HOLD_REASON));
}
public RoleModel createRoleModel(Role role, List<String> assignedUsers, List<String> assignedGroups)
{
return new RoleModel(role.getName(),
role.getDisplayLabel(),
role.getCapabilities()
.stream()
.map(this::createCapabilityModel)
.sorted(Comparator.comparing(CapabilityModel::name))
.toList(),
role.getRoleGroupName(),
role.getGroupShortName(),
assignedUsers,
assignedGroups);
}
public CapabilityModel createCapabilityModel(Capability capability)
{
return new CapabilityModel(capability.getName(), capability.getTitle(), capability.getDescription(),
createGroupModel(capability.getGroup()), capability.getIndex());
}
public GroupModel createGroupModel(Group group)
{
if (group == null)
{
return null;
}
return new GroupModel(group.getId(), group.getTitle());
}
/**
* Creates an object of type FilePlan
*

View File

@@ -0,0 +1,264 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService;
import org.alfresco.module.org_alfresco_module_rm.role.Role;
import org.alfresco.rest.antlr.WhereClauseParser;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.resource.parameters.where.Query;
import org.alfresco.rest.framework.resource.parameters.where.QueryHelper;
import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalker;
import org.alfresco.rm.rest.api.RMRoles;
import org.alfresco.rm.rest.api.model.RoleModel;
import org.alfresco.service.cmr.repository.NodeRef;
public class RMRolesImpl implements RMRoles
{
private ApiNodesModelFactory nodesModelFactory;
private FilePlanRoleService filePlanRoleService;
private static final Set<String> LIST_ROLES_QUERY_PROPERTIES = new HashSet<>(List.of(PARAM_PERSON_ID, PARAM_INCLUDE_SYSTEM_ROLES, PARAM_CAPABILITY_NAME));
@Override
public CollectionWithPagingInfo<RoleModel> getRoles(NodeRef filePlan, Parameters parameters)
{
var rolesFilter = getRolesFilter(parameters.getQuery());
var roles = getRolesByFilter(filePlan, rolesFilter);
var filteredRoles = roles.stream()
.map(role -> createRoleModel(filePlan, role, parameters.getInclude()))
.filter(hasRoleCapabilities(rolesFilter.getCapabilities()))
.toList();
var page = filteredRoles
.stream()
.sorted(Comparator.comparing(RoleModel::name))
.skip(parameters.getPaging().getSkipCount())
.limit(parameters.getPaging().getMaxItems())
.collect(Collectors.toCollection(LinkedList::new));
int totalItems = filteredRoles.size();
boolean hasMore = parameters.getPaging().getSkipCount() + parameters.getPaging().getMaxItems() < totalItems;
return CollectionWithPagingInfo.asPaged(parameters.getPaging(), page, hasMore, totalItems);
}
private Predicate<RoleModel> hasRoleCapabilities(List<String> capabilities)
{
return role -> capabilities == null ||
capabilities.isEmpty() ||
role.capabilities().stream().anyMatch(capability -> capabilities.contains(capability.name()));
}
private Set<Role> getRolesByFilter(NodeRef filePlan, RolesFilter rolesFilter)
{
if (rolesFilter.getPersonId() != null)
{
return filePlanRoleService.getRolesByUser(filePlan, rolesFilter.getPersonId(), rolesFilter.includeSystemRoles());
}
else
{
return filePlanRoleService.getRoles(filePlan, rolesFilter.includeSystemRoles());
}
}
private RoleModel createRoleModel(NodeRef filePlan, Role role, List<String> include)
{
List<String> assignedUsers = getAssignedUsers(filePlan, role, include);
List<String> assignedGroups = getAssignedGroups(filePlan, role, include);
return nodesModelFactory.createRoleModel(role, assignedUsers, assignedGroups);
}
private List<String> getAssignedUsers(NodeRef filePlan, Role role, List<String> include)
{
if (include != null && include.contains(PARAM_INCLUDE_ASSIGNED_USERS))
{
return new ArrayList<>(filePlanRoleService.getAllAssignedToRole(filePlan, role.getName()));
}
return null;
}
private List<String> getAssignedGroups(NodeRef filePlan, Role role, List<String> include)
{
if (include != null && include.contains(PARAM_INCLUDE_ASSIGNED_GROUPS))
{
return new ArrayList<>(filePlanRoleService.getGroupsAssignedToRole(filePlan, role.getName()));
}
return null;
}
public void setNodesModelFactory(ApiNodesModelFactory nodesModelFactory)
{
this.nodesModelFactory = nodesModelFactory;
}
public void setFilePlanRoleService(FilePlanRoleService filePlanRoleService)
{
this.filePlanRoleService = filePlanRoleService;
}
private RolesFilter getRolesFilter(Query queryParameters)
{
var rolesFilterBuilder = RolesFilter.builder();
if (queryParameters != null)
{
var propertyWalker = new RolesQueryWalker();
QueryHelper.walk(queryParameters, propertyWalker);
rolesFilterBuilder
.withPersonId(propertyWalker.getPersonId())
.withCapabilities(propertyWalker.getCapabilitiesNames())
.withIncludeSystemRoles(propertyWalker.includeSystemRoles());
}
return rolesFilterBuilder.build();
}
private static class RolesQueryWalker extends MapBasedQueryWalker
{
private List<String> capabilitiesNames;
public RolesQueryWalker()
{
super(LIST_ROLES_QUERY_PROPERTIES, null);
}
@Override
public void in(String propertyName, boolean negated, String... propertyValues)
{
if (negated)
{
throw new InvalidArgumentException("Cannot use NOT for " + propertyName);
}
if (PARAM_CAPABILITY_NAME.equalsIgnoreCase(propertyName))
{
capabilitiesNames = Arrays.asList(propertyValues);
}
}
@Override
public void and()
{
// allow AND, e.g. personId='123' AND includeSystemRoles=true
}
public List<String> getCapabilitiesNames()
{
return this.capabilitiesNames;
}
public String getPersonId()
{
return getProperty(PARAM_PERSON_ID, WhereClauseParser.EQUALS, String.class);
}
public Boolean includeSystemRoles()
{
return getProperty(PARAM_INCLUDE_SYSTEM_ROLES, WhereClauseParser.EQUALS, Boolean.class);
}
}
}
class RolesFilter
{
private String personId;
private boolean includeSystemRoles;
private List<String> capabilities;
private RolesFilter()
{}
public static RolesFilterBuilder builder()
{
return new RolesFilterBuilder();
}
public String getPersonId()
{
return personId;
}
public boolean includeSystemRoles()
{
return includeSystemRoles;
}
public List<String> getCapabilities()
{
return capabilities;
}
public static class RolesFilterBuilder
{
private String personId;
private boolean includeSystemRoles = true;
private List<String> capabilities;
public RolesFilterBuilder withPersonId(String personId)
{
this.personId = personId;
return this;
}
public RolesFilterBuilder withIncludeSystemRoles(Boolean includeSystemRoles)
{
if (includeSystemRoles != null)
{
this.includeSystemRoles = includeSystemRoles;
}
return this;
}
public RolesFilterBuilder withCapabilities(List<String> capabilities)
{
this.capabilities = capabilities;
return this;
}
public RolesFilter build()
{
RolesFilter rolesFilter = new RolesFilter();
rolesFilter.personId = this.personId;
rolesFilter.includeSystemRoles = this.includeSystemRoles;
rolesFilter.capabilities = this.capabilities;
return rolesFilter;
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api.model;
public record CapabilityModel(String name, String title, String description, GroupModel group, int index)
{}

View File

@@ -0,0 +1,30 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api.model;
public record GroupModel(String id, String title)
{}

View File

@@ -0,0 +1,32 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api.model;
import java.util.List;
public record RoleModel(String name, String displayLabel, List<CapabilityModel> capabilities, String roleGroupName, String groupShortName, List<String> assignedUsers, List<String> assignedGroups)
{}

View File

@@ -0,0 +1,32 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api.model;
import java.util.List;
public record RoleModelList(List<RoleModel> roleModelList)
{}

View File

@@ -0,0 +1,37 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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 info that defines the Information Governance Roles REST API
*
* @author Damian Ujma
*/
@WebApi(name = "gs", scope = Api.SCOPE.PUBLIC, version = 1)
package org.alfresco.rm.rest.api.roles;
import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.WebApi;

View File

@@ -0,0 +1,177 @@
/*
* #%L
* Alfresco Records Management Module
* %%
* 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.rm.rest.api.impl;
import org.alfresco.module.org_alfresco_module_rm.capability.Capability;
import org.alfresco.module.org_alfresco_module_rm.role.FilePlanRoleService;
import org.alfresco.module.org_alfresco_module_rm.role.Role;
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.tools.RecognizedParamsExtractor;
import org.alfresco.rm.rest.api.model.CapabilityModel;
import org.alfresco.rm.rest.api.model.RoleModel;
import org.alfresco.service.cmr.repository.NodeRef;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import java.util.Set;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class RMRolesImplUnitTest extends BaseUnitTest {
private final RecognizedParamsExtractor queryExtractor = new RecognizedParamsExtractor() {};
private RMRolesImpl rmRolesImpl;
private FilePlanRoleService mockedFilePlanRoleService;
private ApiNodesModelFactory mockedNodesModelFactory;
private final Capability viewRecordsCapability = mock(Capability.class);
private final Capability editMetadataCapability = mock(Capability.class);
private final Role role1 = new Role("Role1", "Role 1", Set.of(viewRecordsCapability), "Group1");
private final Role role2 = new Role("Role2", "Role 2", Set.of(editMetadataCapability), "Group2");
private final RoleModel roleModel1 = new RoleModel("Role1", "Role 1", List.of(new CapabilityModel("ViewRecords", "", "", null, 0)), "Group1", null, List.of("User1"), List.of("Group1"));
private final RoleModel roleModel2 = new RoleModel("Role2", "Role 2", List.of(new CapabilityModel("EditMetadata", "", "", null, 0)), "Group2", null, List.of("User2"), List.of("Group2"));
private final NodeRef filePlan = new NodeRef("workspace://SpacesStore/testFilePlan");
private final Parameters parameters = mock(Parameters.class);
private final Paging paging = mock(Paging.class);
@Before
public void setUp() {
mockedFilePlanRoleService = mock(FilePlanRoleService.class);
mockedNodesModelFactory = mock(ApiNodesModelFactory.class);
rmRolesImpl = new RMRolesImpl();
rmRolesImpl.setFilePlanRoleService(mockedFilePlanRoleService);
rmRolesImpl.setNodesModelFactory(mockedNodesModelFactory);
when(mockedFilePlanRoleService.getRoles(filePlan, true)).thenReturn(Set.of(role1, role2));
when(mockedNodesModelFactory.createRoleModel(eq(role1), any(), any())).thenReturn(roleModel1);
when(mockedNodesModelFactory.createRoleModel(eq(role2), any(), any())).thenReturn(roleModel2);
when(viewRecordsCapability.getName()).thenReturn("ViewRecords");
when(editMetadataCapability.getName()).thenReturn("EditMetadata");
when(parameters.getPaging()).thenReturn(paging);
when(paging.getSkipCount()).thenReturn(0);
when(paging.getMaxItems()).thenReturn(10);
}
@Test
public void testGetRoles_NoFilters() {
// when
CollectionWithPagingInfo<RoleModel> result = rmRolesImpl.getRoles(filePlan, parameters);
// then
List<RoleModel> roleModelList = (List<RoleModel>) result.getCollection();
assertEquals(2, (int) result.getTotalItems());
assertEquals(List.of(roleModel1, roleModel2), roleModelList);
verify(mockedFilePlanRoleService).getRoles(filePlan, true);
verify(mockedNodesModelFactory).createRoleModel(eq(role1), any(), any());
verify(mockedNodesModelFactory).createRoleModel(eq(role2), any(), any());
}
@Test
public void testGetRoles_WithPersonId() {
// given
String personId = "testUser";
when(mockedFilePlanRoleService.getRolesByUser(filePlan, personId, true)).thenReturn(Set.of(role1));
when(parameters.getQuery()).thenReturn(queryExtractor.getWhereClause("(personId='" + personId + "')"));
// when
CollectionWithPagingInfo<RoleModel> result = rmRolesImpl.getRoles(filePlan, parameters);
// then
assertEquals(1, (int) result.getTotalItems());
assertEquals(List.of(roleModel1), result.getCollection());
verify(mockedFilePlanRoleService).getRolesByUser(filePlan, personId, true);
verify(mockedNodesModelFactory).createRoleModel(eq(role1), any(), any());
}
@Test
public void testGetNonSystemRoles() {
//given
when(mockedFilePlanRoleService.getRoles(filePlan, false)).thenReturn(Set.of(role2));
when(parameters.getQuery()).thenReturn(queryExtractor.getWhereClause("(systemRoles=false)"));
// when
CollectionWithPagingInfo<RoleModel> result = rmRolesImpl.getRoles(filePlan, parameters);
// then
assertEquals(1, (int) result.getTotalItems());
assertEquals(List.of(roleModel2), result.getCollection());
verify(mockedFilePlanRoleService).getRoles(filePlan, false);
verify(mockedNodesModelFactory).createRoleModel(eq(role2), any(), any());
}
@Test
public void testGetRoles_WithCapabilitiesFilter() {
// given
when(parameters.getQuery()).thenReturn(queryExtractor.getWhereClause("(capabilityName IN ('ViewRecords'))"));
// when
CollectionWithPagingInfo<RoleModel> result = rmRolesImpl.getRoles(filePlan, parameters);
// then
assertEquals(1, (int) result.getTotalItems());
assertEquals(List.of(roleModel1), result.getCollection());
verify(mockedFilePlanRoleService).getRoles(filePlan, true);
verify(mockedNodesModelFactory).createRoleModel(eq(role1), any(), any());
}
@Test
public void testGetRoles_IncludeAssignedUsersAndGroups() {
// given
when(mockedFilePlanRoleService.getRoles(filePlan, true)).thenReturn(Set.of(role1));
when(mockedFilePlanRoleService.getAllAssignedToRole(filePlan, "Role1")).thenReturn(Set.of("User1"));
when(mockedFilePlanRoleService.getGroupsAssignedToRole(filePlan, "Role1")).thenReturn(Set.of("Group1"));
when(parameters.getInclude()).thenReturn(List.of("assignedUsers", "assignedGroups"));
// when
CollectionWithPagingInfo<RoleModel> result = rmRolesImpl.getRoles(filePlan, parameters);
// then
List<RoleModel> roleModelList = (List<RoleModel>) result.getCollection();
assertEquals(1, (int) result.getTotalItems());
assertEquals(List.of(roleModel1), roleModelList);
assertEquals(List.of("User1"), roleModelList.get(0).assignedUsers());
assertEquals(List.of("Group1"), roleModelList.get(0).assignedGroups());
verify(mockedFilePlanRoleService).getRoles(filePlan, true);
verify(mockedFilePlanRoleService).getAllAssignedToRole(filePlan, "Role1");
verify(mockedFilePlanRoleService).getGroupsAssignedToRole(filePlan, "Role1");
verify(mockedNodesModelFactory).createRoleModel(role1, List.of("User1"), List.of("Group1"));
}
}

View File

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

View File

@@ -540,6 +540,66 @@ paths:
description: Unexpected error
schema:
$ref: '#/definitions/Error'
'/file-plans/{filePlanId}/roles':
get:
tags:
- file-plans
summary: Get roles in a file plan
description: |
Gets a list of roles for the specified file plan **filePlanId**.
You can use the **include** parameter to return additional information.
The **where** parameter can also be used to filter by **personId**, **systemRoles** and **capabilityName**.
You can use the **where** parameter to
* filter roles by user:
`where=(personId='admin')`
* not include system roles in the results:
`where=(systemRoles=false)`
* filter roles by specified capabilities:
`where=(capabilityName in ('AddToHold', 'ViewRecords'))`
This may be combined with all filter, as shown below:
`where=(systemRoles=false and personId='johndoe' and capabilityName in ('AddToHold', 'ViewRecords'))`
operationId: getFilePlanRoles
parameters:
- $ref: '#/parameters/filePlanIdWithAliasParam'
- $ref: '#/parameters/whereParam'
- $ref: '#/parameters/rolesIncludeParam'
- $ref: '#/parameters/skipCountParam'
- $ref: '#/parameters/maxItemsParam'
produces:
- application/json
responses:
'200':
description: Successful response
schema:
type: object
properties:
list:
type: array
items:
$ref: '#/definitions/RoleModel'
pagination:
$ref: '#/definitions/Pagination'
'400':
description: |
Invalid parameter: value of **maxItems** or **skipCount**, or **include**, or **where** is invalid
'401':
description: Authentication failed
'403':
description: Current user does not have permission to read **filePlanId**
'404':
description: "**filePlanId** does not exist"
default:
description: Unexpected error
schema:
$ref: '#/definitions/Error'
## Unfiled records containers
'/unfiled-containers/{unfiledContainerId}':
get:
@@ -3307,6 +3367,21 @@ parameters:
* actions
required: false
type: string
rolesIncludeParam:
name: include
in: query
description: |
Returns additional information about roles. Any optional field from the response model can be requested. For example:
* assignedUsers
* assignedGroups
required: false
type: string
whereParam:
name: where
in: query
description: A string to restrict the returned objects by using a predicate.
required: false
type: string
definitions:
FilePlanComponentBodyUpdate:
type: object
@@ -4389,6 +4464,49 @@ definitions:
query:
description: The query which may have been generated in some way from the userQuery
type: string
CapabilityModel:
type: object
properties:
name:
type: string
title:
type: string
description:
type: string
group:
$ref: '#/definitions/GroupModel'
index:
type: integer
GroupModel:
type: object
properties:
id:
type: string
title:
type: string
RoleModel:
type: object
properties:
name:
type: string
displayLabel:
type: string
capabilities:
type: array
items:
$ref: '#/definitions/CapabilityModel'
roleGroupName:
type: string
groupShortName:
type: string
assignedUsers:
type: array
items:
type: string
assignedGroups:
type: array
items:
type: string
HoldBulkOperation:
type: object
properties:
@@ -4929,4 +5047,4 @@ definitions:
- SiteConsumer
- SiteCollaborator
- SiteContributor
- SiteManager
- SiteManager

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -49,5 +49,6 @@ then
echo "Docker Compose started ok"
else
echo "Docker Compose failed to start" >&2
docker compose ${DOCKER_COMPOSES} logs --tail 200
exit 1
fi
fi

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>25.2.0.17</version>
<version>25.2.0.42-SNAPSHOT</version>
</parent>
<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
@@ -747,7 +747,7 @@ public class CreateRulesTests extends RulesRestTest
.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.");
}
/**

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
<!-- Resource defaultTransactionIsolation="-1" defaultAutoCommit="false" maxActive="100" initialSize="10" password="alfresco" username="alfresco" url="jdbc:mysql:///alfresco" driverClassName="org.gjt.mm.mysql.Driver" type="javax.sql.DataSource" auth="Container" name="jdbc/dataSource"/-->
<Environment override="false" type="java.lang.Boolean" name="properties/startup.enable" description="A flag that globally enables or disables startup of the major Alfresco subsystems." value="true"/>
<Environment override="false" type="java.lang.String" name="properties/dir.root" description="The filesystem directory below which content and index data is stored. Should be on a shared disk if this is a clustered installation."/>
<Environment override="false" type="java.lang.String" name="properties/hibernate.dialect" description="The fully qualified name of a org.hibernate.dialect.Dialect subclass that allows Hibernate to generate SQL optimized for a particular relational database. Choose from org.hibernate.dialect.DerbyDialect, org.hibernate.dialect.MySQLInnoDBDialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9Dialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect, org.hibernate.dialect.PostgreSQLDialect"/>
<Environment override="false" type="java.lang.String" name="properties/hibernate.dialect" description="The fully qualified name of a org.hibernate.dialect.Dialect subclass that allows Hibernate to generate SQL optimized for a particular relational database. Choose from org.hibernate.dialect.DerbyDialect, org.hibernate.dialect.MySQLInnoDBDialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9Dialect, org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect, org.alfresco.repo.domain.hibernate.dialect.SQLServerDialect, org.hibernate.dialect.PostgreSQLDialect"/>
<Environment override="false" type="java.lang.String" name="properties/hibernate.query.substitutions" description="Mapping from tokens in Hibernate queries to SQL tokens. For PostgreSQL, set this to &quot;true TRUE, false FALSE&quot;."/>
<Environment override="false" type="java.lang.Boolean" name="properties/hibernate.jdbc.use_get_generated_keys" description="Enable use of JDBC3 PreparedStatement.getGeneratedKeys() to retrieve natively generated keys after insert. Requires JDBC3+ driver. Set to false if your driver has problems with the Hibernate identifier generators. By default, tries to determine the driver capabilities using connection metadata."/>
<Environment override="false" type="java.lang.String" name="properties/hibernate.default_schema" description="Qualify unqualified table names with the given schema/tablespace in generated SQL. It may be necessary to set this when the target database has more than one schema."/>

View File

@@ -500,7 +500,7 @@
org.hibernate.dialect.MySQLInnoDBDialect,
org.alfresco.repo.domain.hibernate.dialect.AlfrescoOracle9Dialect,
org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect,
org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect, org.hibernate.dialect.PostgreSQLDialect</description>
org.alfresco.repo.domain.hibernate.dialect.SQLServerDialect, org.hibernate.dialect.PostgreSQLDialect</description>
<env-entry-name>properties/hibernate.dialect</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value/> <!-- Empty value included for JBoss compatibility -->

21
pom.xml
View File

@@ -2,10 +2,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>alfresco-community-repo</artifactId>
<version>25.2.0.17</version>
<version>25.2.0.42-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-super-pom</artifactId>
@@ -58,7 +59,7 @@
<dependency.aspectj.version>1.9.22.1</dependency.aspectj.version>
<dependency.spring.version>6.2.2</dependency.spring.version>
<dependency.spring-security.version>6.3.7</dependency.spring-security.version>
<dependency.spring-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>
@@ -74,8 +75,9 @@
<dependency.guava.version>33.3.1-jre</dependency.guava.version>
<dependency.httpclient.version>4.5.14</dependency.httpclient.version>
<dependency.httpcore.version>4.4.16</dependency.httpcore.version>
<dependency.httpcomponents-httpclient5.version>5.4.1</dependency.httpcomponents-httpclient5.version>
<dependency.httpcomponents-httpcore5.version>5.3.3</dependency.httpcomponents-httpcore5.version>
<dependency.httpcomponents-httpclient5.version>5.5</dependency.httpcomponents-httpclient5.version>
<dependency.httpcomponents-httpcore5.version>5.3.4</dependency.httpcomponents-httpcore5.version>
<dependency.httpcomponents-httpcore5-h2.version>5.3.4</dependency.httpcomponents-httpcore5-h2.version>
<dependency.commons-httpclient.version>3.1-HTTPCLIENT-1265</dependency.commons-httpclient.version>
<dependency.xercesImpl.version>2.12.2</dependency.xercesImpl.version>
<dependency.slf4j.version>2.0.16</dependency.slf4j.version>
@@ -83,9 +85,9 @@
<dependency.groovy.version>3.0.23</dependency.groovy.version>
<dependency.tika.version>2.9.2</dependency.tika.version>
<dependency.truezip.version>7.7.10</dependency.truezip.version>
<dependency.poi.version>5.3.0</dependency.poi.version>
<dependency.poi.version>5.4.0</dependency.poi.version>
<dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version>
<dependency.camel.version>4.6.0</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
<dependency.camel.version>4.11.0</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
<dependency.netty.version>4.1.118.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
<dependency.activemq.version>5.18.6</dependency.activemq.version>
<dependency.apache-compress.version>1.27.1</dependency.apache-compress.version>
@@ -153,7 +155,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>25.2.0.17</tag>
<tag>HEAD</tag>
</scm>
<distributionManagement>
@@ -400,6 +402,11 @@
<artifactId>httpcore5</artifactId>
<version>${dependency.httpcomponents-httpcore5.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5-h2</artifactId>
<version>${dependency.httpcomponents-httpcore5-h2.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>25.2.0.17</version>
<version>25.2.0.42-SNAPSHOT</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
@@ -46,7 +46,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;
@@ -71,9 +71,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;
@@ -97,6 +99,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;
@@ -118,11 +130,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)
{
@@ -136,6 +154,8 @@ 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);
@@ -156,24 +176,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)
{
@@ -252,38 +295,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()
@@ -296,7 +364,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)
@@ -310,7 +383,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();
@@ -322,6 +395,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)
@@ -417,7 +507,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

@@ -712,7 +712,7 @@ public class PeopleImpl implements People
Boolean isEnabled = person.isEnabled();
if (isEnabled != null)
{
if (isAdminAuthority(personIdToUpdate))
if (!isEnabled && isAdminAuthority(personIdToUpdate))
{
throw new PermissionDeniedException("Admin authority cannot be disabled.");
}

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
@@ -75,6 +75,7 @@ public class ActionNodeParameterValidator implements ActionValidator
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;
@@ -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);
});
@@ -169,4 +178,5 @@ public class ActionNodeParameterValidator implements ActionValidator
throw new InvalidArgumentException(NOT_A_CATEGORY + nodeRef.getId());
}
}
}

View File

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

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

@@ -205,7 +205,7 @@ public class ProcessesImplTest extends TestCase implements RecognizedParamsExtra
{
// the tests are always run on PostgreSQL only
// Dialect dialect = (Dialect) applicationContext.getBean("dialect");
// if (dialect instanceof AlfrescoSQLServerDialect)
// if (dialect instanceof SQLServerDialect)
// {
// REPO-1104: we do not run this test on MS SQL server because it will fail
// until the Activiti defect related to REPO-1104 will be fixed

View File

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

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;
@@ -366,7 +367,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

@@ -63,13 +63,7 @@ import org.alfresco.repo.security.permissions.PermissionCheckedValue.PermissionC
import org.alfresco.service.Auditable;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.model.FileExistsException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileFolderServiceType;
import org.alfresco.service.cmr.model.FileFolderUtil;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.alfresco.service.cmr.model.SubFolderFilter;
import org.alfresco.service.cmr.model.*;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
@@ -1314,7 +1308,7 @@ public class FileFolderServiceImpl extends AbstractBaseCopyService implements Fi
}
catch (DuplicateChildNodeNameException e)
{
throw new FileExistsException(parentNodeRef, name);
throw new FileExistsException(parentNodeRef, name, typeQName.getLocalName());
}
NodeRef nodeRef = assocRef.getChildRef();

View File

@@ -31,12 +31,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.
*
* @param request
* the request
* @param response
* the response
* Gets an externally authenticated user ID from the HTTP request.
*
* @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

@@ -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;
@@ -50,9 +49,8 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
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,20 +37,12 @@ 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;
@@ -34,12 +34,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

@@ -248,4 +248,9 @@ public interface AuthorityDAO
* Remove an authority from zones.
*/
public void removeAuthorityFromZones(String authorityName, Set<String> zones);
/**
* @return Returns the authority container, <b>which must exist</b>
*/
NodeRef getAuthorityContainer();
}

View File

@@ -1360,7 +1360,8 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
/**
* @return Returns the authority container, <b>which must exist</b>
*/
private NodeRef getAuthorityContainer()
@Override
public NodeRef getAuthorityContainer()
{
return getSystemContainer(qnameAssocAuthorities);
}

View File

@@ -411,6 +411,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
Date groupLastModified = groupLastModifiedMillis == -1 ? null : new Date(groupLastModifiedMillis);
Date personLastModified = personLastModifiedMillis == -1 ? null : new Date(personLastModifiedMillis);
plugin.initSync(groupLastModified, syncDelete);
ret.setGroups(plugin.getGroupNames());
ret.setUsers(plugin.getPersonNames());
@@ -918,7 +919,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
: getMostRecentUpdateTime(
ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, splitTxns);
Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis);
userRegistry.initSync(lastModified, syncDelete);
if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled())
{
if (lastModified == null)
@@ -945,6 +946,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
this.loggingInterval);
class Analyzer extends BaseBatchProcessWorker<NodeDescription>
{
private final Map<String, NodeDescription> nodeDescriptions = new HashMap<>();
private final Map<String, String> groupsToCreate = new TreeMap<String, String>();
private final Map<String, Set<String>> personParentAssocsToCreate = newPersonMap();
private final Map<String, Set<String>> personParentAssocsToDelete = newPersonMap();
@@ -1103,6 +1105,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
{
PropertyMap groupProperties = group.getProperties();
String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME);
nodeDescriptions.put(groupName, group);
String groupDisplayName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME);
if (groupDisplayName == null)
{
@@ -1565,9 +1568,11 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
+ groupShortName + "'");
}
// create the group
Map<QName, Serializable> groupProperties = Optional.ofNullable(Analyzer.this.nodeDescriptions.get(child))
.map(NodeDescription::getProperties)
.orElse(new PropertyMap());
ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(
AuthorityType.getAuthorityType(child), groupShortName, groupDisplayName,
zoneSet);
AuthorityType.getAuthorityType(child), groupShortName, groupDisplayName, zoneSet, groupProperties);
}
else
{

View File

@@ -4,21 +4,21 @@
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
@@ -33,14 +33,14 @@ import org.alfresco.service.namespace.QName;
/**
* A <code>UserRegistry</code> is an encapsulation of an external registry from which user and group information can be queried (typically an LDAP directory). Implementations may optional support the ability to query only those users and groups modified since a certain time.
*
*
* @author dward
*/
public interface UserRegistry
{
/**
* Gets descriptions of all the persons (users) in the user registry or all those changed since a certain date.
*
*
* @param modifiedSince
* if non-null, then only descriptions of users modified since this date should be returned; if <code>null</code> then descriptions of all users should be returned.
* @return a {@link Collection} of {@link NodeDescription}s of all the persons (users) in the user registry or all those changed since a certain date. The description properties should correspond to those of an Alfresco person node.
@@ -49,7 +49,7 @@ public interface UserRegistry
/**
* Gets descriptions of all the groups in the user registry or all those changed since a certain date.
*
*
* @param modifiedSince
* if non-null, then only descriptions of groups modified since this date should be returned; if <code>null</code> then descriptions of all groups should be returned.
* @return a {@link Collection} of {@link NodeDescription}s of all the groups in the user registry or all those changed since a certain date. The description properties should correspond to those of an Alfresco authority node.
@@ -58,22 +58,35 @@ public interface UserRegistry
/**
* Gets the names of all persons in the registry. Used to detect local persons to be deleted. Note that the treatment of these names will depend on Alfresco's username case-sensitivity setting.
*
*
* @return the person names
*/
public Collection<String> getPersonNames();
/**
* Gets the names of all groups in the registry. Used to detect local groups to be deleted.
*
*
* @return the person names
*/
public Collection<String> getGroupNames();
/**
* Gets the set of property names that are auto-mapped by this user registry. These should remain read-only for this registry's users in the UI.
*
*
* @return the person mapped properties
*/
public Set<QName> getPersonMappedProperties();
/**
* Notifies the user registry that the sync process is about to start.
*
* @param modifiedSince
* if non-null, then only descriptions of groups and users modified since this date should be returned; if <code>null</code> then descriptions of all groups and users should be returned.
* @param syncDelete
* if <code>true</code> then registry will be queried for all users and groups to calculate deleted entities
*/
default void initSync(Date modifiedSince, boolean syncDelete)
{
// default implementation does nothing
}
}

View File

@@ -914,7 +914,7 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic
String[] tokenizedFilter = SearchLanguageConversion.tokenizeString(escNameFilter);
// cm:name
query.append(" cm:name:\" ");
query.append(" cm:name:\"");
for (int i = 0; i < tokenizedFilter.length; i++)
{
if (i != 0) // Not first element

View File

@@ -43,6 +43,7 @@ public class FileExistsException extends AlfrescoRuntimeException
private NodeRef parentNodeRef;
private String name;
private String type;
public FileExistsException(NodeRef parentNodeRef, String name)
{
@@ -51,6 +52,14 @@ public class FileExistsException extends AlfrescoRuntimeException
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;
@@ -60,4 +69,9 @@ public class FileExistsException extends AlfrescoRuntimeException
{
return name;
}
public String getType()
{
return type;
}
}

View File

@@ -0,0 +1,62 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* 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. -->

View File

@@ -141,9 +141,10 @@
<property name="childByNameCache" ref="node.childByNameCache"/>
<property name="cachingThreshold" value="${nodes.bulkLoad.cachingThreshold}"/>
</bean>
<bean id="nodeDAO.org.alfresco.repo.domain.dialect.Dialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl" parent="nodeDAObase" />
<bean id="nodeDAO.org.alfresco.repo.domain.dialect.MySQLInnoDBDialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl$MySQL" parent="nodeDAO.org.alfresco.repo.domain.dialect.Dialect" />
<bean id="nodeDAO.org.alfresco.repo.domain.dialect.AlfrescoSQLServerDialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl$MSSQL" parent="nodeDAO.org.alfresco.repo.domain.dialect.Dialect" />
<bean id="nodeDAO.org.alfresco.repo.domain.dialect.SQLServerDialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl$MSSQL" parent="nodeDAO.org.alfresco.repo.domain.dialect.Dialect" />
<!-- WARNING: Experimental/unsupported - see MySQLClusterNDBDialect ! -->
<bean id="nodeDAO.org.alfresco.repo.domain.dialect.AlfrescoMySQLClusterNDBDialect" class="org.alfresco.repo.domain.node.ibatis.NodeDAOImpl$MySQLClusterNDB" parent="nodeDAO.org.alfresco.repo.domain.dialect.Dialect" />

View File

@@ -563,6 +563,7 @@ authentication.ticket.validDuration=PT1H
authentication.ticket.useSingleTicketPerUser=true
authentication.alwaysAllowBasicAuthForAdminConsole.enabled=true
authentication.alwaysAllowBasicAuthForWebScriptsHome.enabled=true
authentication.getRemoteUserTimeoutMilliseconds=10000
# FTP access

View File

@@ -104,4 +104,7 @@
<ref bean="transactionService" />
</property>
</bean>
</beans>
<bean id="webScriptsHomeAuthenticator" class="org.alfresco.repo.security.authentication.external.DefaultWebScriptsHomeAuthenticator" />
</beans>

View File

@@ -170,6 +170,9 @@
<property name="adminConsoleScopes">
<value>${identity-service.admin-console.scopes:openid,profile,email,offline_access}</value>
</property>
<property name="webScriptsHomeScopes">
<value>${identity-service.webscripts-home.scopes:openid,profile,email,offline_access}</value>
</property>
<property name="passwordGrantScopes">
<value>${identity-service.password-grant.scopes:openid,profile,email}</value>
</property>
@@ -179,6 +182,9 @@
<property name="jwtClockSkewMs">
<value>${identity-service.jwt-clock-skew-ms:0}</value>
</property>
<property name="webScriptsHomeRedirectPath">
<value>${identity-service.webscripts-home.redirect-path}</value>
</property>
</bean>
<!-- Enable control over mapping between request and user ID -->
@@ -197,12 +203,12 @@
</property>
</bean>
<bean id="adminConsoleAuthenticationCookiesService" class="org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleAuthenticationCookiesService">
<constructor-arg ref="sysAdminParams" />
<constructor-arg value="${admin.console.cookie.lifetime:86400}" />
</bean>
<bean id="adminAuthenticationCookiesService" class="org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesService">
<constructor-arg ref="sysAdminParams" />
<constructor-arg value="${admin.console.cookie.lifetime:86400}" />
</bean>
<bean id="adminConsoleAuthenticator" class="org.alfresco.repo.security.authentication.identityservice.admin.IdentityServiceAdminConsoleAuthenticator">
<bean id="adminConsoleAuthenticator" class="org.alfresco.repo.security.authentication.identityservice.authentication.admin.IdentityServiceAdminConsoleAuthenticator">
<property name="active">
<value>${identity-service.authentication.enabled}</value>
</property>
@@ -210,7 +216,7 @@
<ref bean="identityServiceFacade"/>
</property>
<property name="cookiesService">
<ref bean="adminConsoleAuthenticationCookiesService" />
<ref bean="adminAuthenticationCookiesService" />
</property>
<property name="remoteUserMapper">
<ref bean="remoteUserMapper" />
@@ -220,6 +226,24 @@
</property>
</bean>
<bean id="webScriptsHomeAuthenticator" class="org.alfresco.repo.security.authentication.identityservice.authentication.webscripts.IdentityServiceWebScriptsHomeAuthenticator">
<property name="active">
<value>${identity-service.authentication.enabled}</value>
</property>
<property name="identityServiceFacade">
<ref bean="identityServiceFacade"/>
</property>
<property name="cookiesService">
<ref bean="adminAuthenticationCookiesService" />
</property>
<property name="remoteUserMapper">
<ref bean="remoteUserMapper" />
</property>
<property name="identityServiceConfig">
<ref bean="identityServiceConfig" />
</property>
</bean>
<bean id="jitProvisioningHandler" class="org.alfresco.repo.security.authentication.identityservice.IdentityServiceJITProvisioningHandler">
<constructor-arg ref="PersonService"/>
<constructor-arg ref="identityServiceFacade"/>

View File

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

View File

@@ -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
@@ -54,6 +54,7 @@ import org.alfresco.util.testing.category.NonBuildTests;
// From AppContext05TestSuite
org.alfresco.repo.domain.node.NodeDAOTest.class,
org.alfresco.repo.domain.subscriptions.SubscriptionDAOTest.class,
org.alfresco.repo.security.permissions.impl.AclDaoComponentTest.class,
org.alfresco.repo.domain.contentdata.ContentDataDAOTest.class,
org.alfresco.repo.domain.encoding.EncodingDAOTest.class,

View File

@@ -34,11 +34,12 @@ import org.alfresco.repo.security.authentication.identityservice.IdentityService
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceJITProvisioningHandlerUnitTest;
import org.alfresco.repo.security.authentication.identityservice.LazyInstantiatingIdentityServiceFacadeUnitTest;
import org.alfresco.repo.security.authentication.identityservice.SpringBasedIdentityServiceFacadeUnitTest;
import org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleAuthenticationCookiesServiceUnitTest;
import org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleHttpServletRequestWrapperUnitTest;
import org.alfresco.repo.security.authentication.identityservice.admin.IdentityServiceAdminConsoleAuthenticatorUnitTest;
import org.alfresco.repo.security.authentication.identityservice.authentication.AdditionalHeadersHttpServletRequestWrapperUnitTest;
import org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesServiceUnitTest;
import org.alfresco.repo.security.authentication.identityservice.authentication.admin.IdentityServiceAdminConsoleAuthenticatorUnitTest;
import org.alfresco.repo.security.authentication.identityservice.user.AccessTokenToDecodedTokenUserMapperUnitTest;
import org.alfresco.repo.security.authentication.identityservice.user.TokenUserToOIDCUserMapperUnitTest;
import org.alfresco.repo.security.authentication.identityservice.webscript.IdentityServiceWebScriptsHomeAuthenticatorUnitTest;
import org.alfresco.util.testing.category.DBTests;
import org.alfresco.util.testing.category.NonBuildTests;
@@ -153,9 +154,10 @@ import org.alfresco.util.testing.category.NonBuildTests;
IdentityServiceJITProvisioningHandlerUnitTest.class,
AccessTokenToDecodedTokenUserMapperUnitTest.class,
TokenUserToOIDCUserMapperUnitTest.class,
AdminConsoleAuthenticationCookiesServiceUnitTest.class,
AdminConsoleHttpServletRequestWrapperUnitTest.class,
AdminAuthenticationCookiesServiceUnitTest.class,
AdditionalHeadersHttpServletRequestWrapperUnitTest.class,
IdentityServiceAdminConsoleAuthenticatorUnitTest.class,
IdentityServiceWebScriptsHomeAuthenticatorUnitTest.class,
ClientRegistrationProviderUnitTest.class,
org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class,
org.alfresco.repo.security.authentication.PasswordHashingTest.class,

View File

@@ -25,10 +25,7 @@
*/
package org.alfresco.repo.action.executer;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
@@ -48,6 +45,7 @@ import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.model.FolderExistsException;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
@@ -340,6 +338,49 @@ public class ImporterActionExecuterTest
});
}
@Test
public void testDuplicateUnzipping() throws IOException
{
final RetryingTransactionHelper retryingTransactionHelper = serviceRegistry.getRetryingTransactionHelper();
retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Void>() {
@Override
public Void execute() throws Throwable
{
NodeRef rootNodeRef = nodeService.getRootNode(storeRef);
// create test data
NodeRef zipFileNodeRef = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_CONTENT).getChildRef();
NodeRef targetFolderNodeRef = nodeService.createNode(rootNodeRef, ContentModel.ASSOC_CHILDREN, ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_FOLDER).getChildRef();
putContent(zipFileNodeRef, "import-archive-test/accentCharTestZip.zip");
Action action = createAction(zipFileNodeRef, "ImporterActionExecuterTestActionDefinition", targetFolderNodeRef);
try
{
importerActionExecuter.setUncompressedBytesLimit("100000");
importerActionExecuter.execute(action, zipFileNodeRef);
// unzip again to duplicate node
importerActionExecuter.execute(action, zipFileNodeRef);
}
catch (FolderExistsException e)
{
assertTrue(e.getMessage().contains("File or folder accentCharTestZip already exists"));
}
finally
{
// clean test data
nodeService.deleteNode(targetFolderNodeRef);
nodeService.deleteNode(zipFileNodeRef);
}
return null;
}
});
}
private void putContent(NodeRef zipFileNodeRef, String resource)
{
URL url = AbstractContentTransformerTest.class.getClassLoader().getResource(resource);

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
@@ -43,9 +43,9 @@ import org.alfresco.service.cmr.subscriptions.SubscriptionItemTypeEnum;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.testing.category.NeverRunsTests;
import org.alfresco.util.testing.category.DBTests;
@Category({OwnJVMTestsCategory.class, NeverRunsTests.class})
@Category({OwnJVMTestsCategory.class, DBTests.class})
public class SubscriptionDAOTest extends TestCase
{
private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();

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.Collections.enumeration;
import static java.util.Collections.list;
@@ -49,19 +49,18 @@ import org.mockito.Mock;
import org.alfresco.error.AlfrescoRuntimeException;
@SuppressWarnings("PMD.UseDiamondOperator")
public class AdminConsoleHttpServletRequestWrapperUnitTest
public class AdditionalHeadersHttpServletRequestWrapperUnitTest
{
private static final String DEFAULT_HEADER = "default_header";
private static final String DEFAULT_HEADER_VALUE = "default_value";
private static final String ADDITIONAL_HEADER = "additional_header";
private static final String ADDITIONAL_HEADER_VALUE = "additional_value";
private static final Map<String, String> DEFAULT_HEADERS = new HashMap<String, String>() {
private static final Map<String, String> DEFAULT_HEADERS = new HashMap<>() {
{
put(DEFAULT_HEADER, DEFAULT_HEADER_VALUE);
}
};
private static final Map<String, String> ADDITIONAL_HEADERS = new HashMap<String, String>() {
private static final Map<String, String> ADDITIONAL_HEADERS = new HashMap<>() {
{
put(ADDITIONAL_HEADER, ADDITIONAL_HEADER_VALUE);
}
@@ -69,25 +68,25 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
@Mock
private HttpServletRequest request;
private AdminConsoleHttpServletRequestWrapper requestWrapper;
private AdditionalHeadersHttpServletRequestWrapper requestWrapper;
@Before
public void setUp()
{
initMocks(this);
requestWrapper = new AdminConsoleHttpServletRequestWrapper(ADDITIONAL_HEADERS, request);
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(ADDITIONAL_HEADERS, request);
}
@Test(expected = AlfrescoRuntimeException.class)
public void wrapperShouldNotBeInstancedWithoutAdditionalHeaders()
{
new AdminConsoleHttpServletRequestWrapper(null, request);
new AdditionalHeadersHttpServletRequestWrapper(null, request);
}
@Test(expected = IllegalArgumentException.class)
public void wrapperShouldNotBeInstancedWithoutRequestsToWrap()
{
new AdminConsoleHttpServletRequestWrapper(new HashMap<>(), null);
new AdditionalHeadersHttpServletRequestWrapper(new HashMap<>(), null);
}
@Test
@@ -112,7 +111,7 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
{
when(request.getHeaderNames()).thenReturn(enumeration(DEFAULT_HEADERS.keySet()));
requestWrapper = new AdminConsoleHttpServletRequestWrapper(new HashMap<>(), request);
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(new HashMap<>(), request);
Enumeration<String> headerNames = requestWrapper.getHeaderNames();
assertNotNull("headerNames should not be null", headerNames);
assertTrue("headerNames should not be empty", headerNames.hasMoreElements());
@@ -164,7 +163,7 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
Map<String, String> overrideHeaders = new HashMap<>();
overrideHeaders.put(DEFAULT_HEADER, overrideHeaderValue);
requestWrapper = new AdminConsoleHttpServletRequestWrapper(overrideHeaders, request);
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(overrideHeaders, request);
String header = requestWrapper.getHeader(DEFAULT_HEADER);
assertEquals("The header should have the overridden value", overrideHeaderValue, header);
@@ -204,7 +203,7 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
Map<String, String> overrideHeaders = new HashMap<>();
overrideHeaders.put(DEFAULT_HEADER, overrideHeaderValue);
requestWrapper = new AdminConsoleHttpServletRequestWrapper(overrideHeaders, request);
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(overrideHeaders, request);
Enumeration<String> headers = requestWrapper.getHeaders(DEFAULT_HEADER);
assertNotNull("The headers enumeration should not be null", headers);
assertTrue("The headers enumeration should not be empty", headers.hasMoreElements());

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.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -46,7 +46,7 @@ import org.mockito.Mock;
import org.alfresco.repo.admin.SysAdminParams;
public class AdminConsoleAuthenticationCookiesServiceUnitTest
public class AdminAuthenticationCookiesServiceUnitTest
{
private static final int DEFAULT_COOKIE_LIFETIME = 86400;
private static final String COOKIE_NAME = "cookie";
@@ -59,13 +59,13 @@ public class AdminConsoleAuthenticationCookiesServiceUnitTest
private SysAdminParams sysAdminParams;
@Captor
private ArgumentCaptor<Cookie> cookieCaptor;
private AdminConsoleAuthenticationCookiesService cookiesService;
private AdminAuthenticationCookiesService cookiesService;
@Before
public void setUp()
{
initMocks(this);
cookiesService = new AdminConsoleAuthenticationCookiesService(sysAdminParams, DEFAULT_COOKIE_LIFETIME);
cookiesService = new AdminAuthenticationCookiesService(sysAdminParams, DEFAULT_COOKIE_LIFETIME);
}
@Test
@@ -138,7 +138,7 @@ public class AdminConsoleAuthenticationCookiesServiceUnitTest
public void cookieWithCustomMaxAgeShouldBeAddedToTheResponse()
{
int customMaxAge = 60;
cookiesService = new AdminConsoleAuthenticationCookiesService(sysAdminParams, customMaxAge);
cookiesService = new AdminAuthenticationCookiesService(sysAdminParams, customMaxAge);
when(sysAdminParams.getAlfrescoProtocol()).thenReturn("https");
cookiesService.addCookie(COOKIE_NAME, COOKIE_VALUE, response);

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.admin;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -58,11 +58,12 @@ import org.alfresco.repo.security.authentication.identityservice.IdentityService
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
import org.alfresco.repo.security.authentication.identityservice.authentication.AdditionalHeadersHttpServletRequestWrapper;
import org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesService;
@SuppressWarnings("PMD.AvoidStringBufferField")
public class IdentityServiceAdminConsoleAuthenticatorUnitTest
{
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";
@@ -76,7 +77,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
@Mock
IdentityServiceConfig identityServiceConfig;
@Mock
AdminConsoleAuthenticationCookiesService cookiesService;
AdminAuthenticationCookiesService cookiesService;
@Mock
RemoteUserMapper remoteUserMapper;
@Mock
@@ -84,7 +85,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
@Mock
AccessToken accessToken;
@Captor
ArgumentCaptor<AdminConsoleHttpServletRequestWrapper> requestCaptor;
ArgumentCaptor<AdditionalHeadersHttpServletRequestWrapper> requestCaptor;
IdentityServiceAdminConsoleAuthenticator authenticator;
@@ -122,7 +123,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
String.valueOf(Instant.now().plusSeconds(60).toEpochMilli()));
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getAdminConsoleUser(request, response);
String username = authenticator.getUserId(request, response);
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
assertEquals("admin", username);
@@ -143,7 +144,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenReturn(accessTokenAuthorization);
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getAdminConsoleUser(request, response);
String username = authenticator.getUserId(request, response);
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "REFRESHED_JWT_TOKEN", response);
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
@@ -207,7 +208,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenThrow(AuthorizationException.class);
String username = authenticator.getAdminConsoleUser(request, response);
String username = authenticator.getUserId(request, response);
verify(cookiesService).resetCookie(ALFRESCO_ACCESS_TOKEN, response);
verify(cookiesService).resetCookie(ALFRESCO_REFRESH_TOKEN, response);
@@ -228,7 +229,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
.thenReturn(accessTokenAuthorization);
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getAdminConsoleUser(request, response);
String username = authenticator.getUserId(request, response);
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "JWT_TOKEN", response);
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
@@ -241,7 +242,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
{
when(remoteUserMapper.getRemoteUser(request)).thenReturn("admin");
String username = authenticator.getAdminConsoleUser(request, response);
String username = authenticator.getUserId(request, response);
assertEquals("admin", username);
}

View File

@@ -0,0 +1,253 @@
/*
* #%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.webscript;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import java.io.IOException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.nimbusds.oauth2.sdk.Scope;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessToken;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
import org.alfresco.repo.security.authentication.identityservice.authentication.AdditionalHeadersHttpServletRequestWrapper;
import org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesService;
import org.alfresco.repo.security.authentication.identityservice.authentication.webscripts.IdentityServiceWebScriptsHomeAuthenticator;
@SuppressWarnings("PMD.AvoidStringBufferField")
public class IdentityServiceWebScriptsHomeAuthenticatorUnitTest
{
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";
@Mock
HttpServletRequest request;
@Mock
HttpServletResponse response;
@Mock
IdentityServiceFacade identityServiceFacade;
@Mock
IdentityServiceConfig identityServiceConfig;
@Mock
AdminAuthenticationCookiesService cookiesService;
@Mock
RemoteUserMapper remoteUserMapper;
@Mock
AccessTokenAuthorization accessTokenAuthorization;
@Mock
AccessToken accessToken;
@Captor
ArgumentCaptor<AdditionalHeadersHttpServletRequestWrapper> requestCaptor;
IdentityServiceWebScriptsHomeAuthenticator authenticator;
StringBuffer webScriptHomeURL = new StringBuffer("http://localhost:8080/alfresco/s/index");
@Before
public void setup()
{
initMocks(this);
ClientRegistration clientRegistration = mock(ClientRegistration.class);
ProviderDetails providerDetails = mock(ProviderDetails.class);
Scope scope = Scope.parse(Arrays.asList("openid", "profile", "email", "offline_access"));
when(clientRegistration.getProviderDetails()).thenReturn(providerDetails);
when(clientRegistration.getClientId()).thenReturn("alfresco");
when(providerDetails.getAuthorizationUri()).thenReturn("http://localhost:8999/auth");
when(providerDetails.getConfigurationMetadata()).thenReturn(Map.of("scopes_supported", scope));
when(identityServiceFacade.getClientRegistration()).thenReturn(clientRegistration);
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
when(remoteUserMapper.getRemoteUser(request)).thenReturn(null);
authenticator = new IdentityServiceWebScriptsHomeAuthenticator();
authenticator.setActive(true);
authenticator.setIdentityServiceFacade(identityServiceFacade);
authenticator.setCookiesService(cookiesService);
authenticator.setRemoteUserMapper(remoteUserMapper);
authenticator.setIdentityServiceConfig(identityServiceConfig);
}
@Test
public void shouldCallRemoteMapperIfTokenIsInCookies()
{
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().plusSeconds(60).toEpochMilli()));
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getUserId(request, response);
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
assertEquals("admin", username);
assertTrue(authenticator.isActive());
}
@Test
public void shouldRefreshExpiredTokenAndCallRemoteMapper()
{
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().minusSeconds(60).toEpochMilli()));
when(accessToken.getTokenValue()).thenReturn("REFRESHED_JWT_TOKEN");
when(accessToken.getExpiresAt()).thenReturn(Instant.now().plusSeconds(60));
when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken);
when(accessTokenAuthorization.getRefreshTokenValue()).thenReturn("REFRESH_TOKEN");
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenReturn(accessTokenAuthorization);
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getUserId(request, response);
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "REFRESHED_JWT_TOKEN", response);
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
assertEquals("Bearer REFRESHED_JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
assertEquals("admin", username);
}
@Test
public void shouldCallAuthChallengeWebScriptHome() throws IOException
{
String redirectPath = "/alfresco/s/index";
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
when(identityServiceConfig.getWebScriptsHomeScopes()).thenReturn(Set.of("openid", "email", "profile", "offline_access"));
when(identityServiceConfig.getWebScriptsHomeRedirectPath()).thenReturn(redirectPath);
ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class);
String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope="
.formatted("http://localhost:8080", redirectPath);
authenticator.requestAuthentication(request, response);
verify(response).sendRedirect(authenticationRequest.capture());
assertTrue(authenticationRequest.getValue().contains(expectedUri));
assertTrue(authenticationRequest.getValue().contains("openid"));
assertTrue(authenticationRequest.getValue().contains("profile"));
assertTrue(authenticationRequest.getValue().contains("email"));
assertTrue(authenticationRequest.getValue().contains("offline_access"));
assertTrue(authenticationRequest.getValue().contains("state"));
}
@Test
public void shouldCallAuthChallengeWebScriptHomeWithAudience() throws IOException
{
String audience = "http://localhost:8082";
String redirectPath = "/alfresco/s/index";
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
when(identityServiceConfig.getAudience()).thenReturn(audience);
when(identityServiceConfig.getWebScriptsHomeRedirectPath()).thenReturn(redirectPath);
when(identityServiceConfig.getWebScriptsHomeScopes()).thenReturn(Set.of("openid", "email", "profile", "offline_access"));
ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class);
String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope="
.formatted("http://localhost:8080", redirectPath);
authenticator.requestAuthentication(request, response);
verify(response).sendRedirect(authenticationRequest.capture());
assertTrue(authenticationRequest.getValue().contains(expectedUri));
assertTrue(authenticationRequest.getValue().contains("openid"));
assertTrue(authenticationRequest.getValue().contains("profile"));
assertTrue(authenticationRequest.getValue().contains("email"));
assertTrue(authenticationRequest.getValue().contains("offline_access"));
assertTrue(authenticationRequest.getValue().contains("audience=%s".formatted(audience)));
assertTrue(authenticationRequest.getValue().contains("state"));
}
@Test
public void shouldResetCookiesAndCallAuthChallenge() throws IOException
{
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().minusSeconds(60).toEpochMilli()));
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenThrow(AuthorizationException.class);
String username = authenticator.getUserId(request, response);
verify(cookiesService).resetCookie(ALFRESCO_ACCESS_TOKEN, response);
verify(cookiesService).resetCookie(ALFRESCO_REFRESH_TOKEN, response);
verify(cookiesService).resetCookie(ALFRESCO_TOKEN_EXPIRATION, response);
assertNull(username);
}
@Test
public void shouldAuthorizeCodeAndSetCookies()
{
when(request.getParameter("code")).thenReturn("auth_code");
when(accessToken.getTokenValue()).thenReturn("JWT_TOKEN");
when(accessToken.getExpiresAt()).thenReturn(Instant.now().plusSeconds(60));
when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken);
when(accessTokenAuthorization.getRefreshTokenValue()).thenReturn("REFRESH_TOKEN");
when(identityServiceFacade.authorize(
AuthorizationGrant.authorizationCode("auth_code", webScriptHomeURL.toString())))
.thenReturn(accessTokenAuthorization);
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getUserId(request, response);
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "JWT_TOKEN", response);
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
assertEquals("admin", username);
}
@Test
public void shouldExtractUsernameFromAuthorizationHeader()
{
when(remoteUserMapper.getRemoteUser(request)).thenReturn("admin");
String username = authenticator.getUserId(request, response);
assertEquals("admin", username);
}
}

View File

@@ -25,26 +25,19 @@
*/
package org.alfresco.repo.site;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
@@ -56,6 +49,8 @@ import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
import org.alfresco.repo.admin.SysAdminParams;
import org.alfresco.repo.admin.SysAdminParamsImpl;
import org.alfresco.repo.cache.MemoryCache;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.dictionary.DictionaryDAO;
import org.alfresco.repo.dictionary.M2Model;
import org.alfresco.repo.dictionary.M2Property;
@@ -65,6 +60,7 @@ import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory;
import org.alfresco.repo.node.archive.NodeArchiveService;
import org.alfresco.repo.node.getchildren.FilterProp;
import org.alfresco.repo.node.getchildren.FilterPropString;
import org.alfresco.repo.search.EmptyResultSet;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
@@ -78,21 +74,10 @@ import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.dictionary.TypeDefinition;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.CopyService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.ScriptLocation;
import org.alfresco.service.cmr.repository.ScriptService;
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;
import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.repository.*;
import org.alfresco.service.cmr.search.SearchParameters;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.*;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteMemberInfo;
import org.alfresco.service.cmr.site.SiteService;
@@ -3129,4 +3114,34 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
siteService.deleteSite(shortName);
}
@Test
public void testFindSitesQueryWithReservedCharacter()
{
// given
SiteServiceImpl cut = new SiteServiceImpl();
ArgumentCaptor<SearchParameters> searchParametersCaptor = ArgumentCaptor.forClass(SearchParameters.class);
SimpleCache<String, Object> cache = new MemoryCache<>();
cache.put("key.sitehome.noderef", new NodeRef("mock", "mock", "mock"));
cut.setSingletonCache(cache);
SearchService searchService = Mockito.mock(SearchService.class);
cut.setSearchService(searchService);
when(searchService.query(any(SearchParameters.class))).thenReturn(new EmptyResultSet());
// when
cut.findSites("-chu", 5);
// then
verify(searchService).query(searchParametersCaptor.capture());
SearchParameters actualSearchParameters = searchParametersCaptor.getValue();
assertThat(actualSearchParameters.getQuery())
.isEqualTo("+TYPE:\"{http://www.alfresco.org/model/site/1.0}site\""
+ " AND ( cm:name:\"\\-chu*\""
+ " OR cm:title: (\"\\-chu*\" )"
+ " OR cm:description:\"\\-chu\")");
}
}

View File

@@ -31,7 +31,7 @@
</onException>
<transacted />
<loadBalance>
<roundRobin/>
<roundRobinLoadBalancer/>
<to uri="bean:mockExceptionThrowingConsumer"/>
<to uri="bean:mockConsumer"/>
</loadBalance>