mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-29 15:21:53 +00:00 
			
		
		
		
	Compare commits
	
		
			80 Commits
		
	
	
		
			25.2.0.17
			...
			feature/MN
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					84b38af65b | ||
| 
						 | 
					f1862c9636 | ||
| 
						 | 
					231075fd5e | ||
| 
						 | 
					979420879c | ||
| 
						 | 
					db330e28f5 | ||
| 
						 | 
					bae0573636 | ||
| 
						 | 
					8089fc2572 | ||
| 
						 | 
					1c4fe53c0f | ||
| 
						 | 
					7a8aa1a2c1 | ||
| 
						 | 
					e08ba1fd4f | ||
| 
						 | 
					4f40bd0687 | ||
| 
						 | 
					a3578f7baa | ||
| 
						 | 
					8e8b9c868f | ||
| 
						 | 
					f77b3b79e5 | ||
| 
						 | 
					3a7157f4a7 | ||
| 
						 | 
					d6b979f341 | ||
| 
						 | 
					a090de4e71 | ||
| 
						 | 
					03621db30a | ||
| 
						 | 
					766a6def2b | ||
| 
						 | 
					117804fb68 | ||
| 
						 | 
					f03e6761ce | ||
| 
						 | 
					74c8288206 | ||
| 
						 | 
					c0bd0a680b | ||
| 
						 | 
					8645cdc76d | ||
| 
						 | 
					5055eec2df | ||
| 
						 | 
					892f41d6fd | ||
| 
						 | 
					075b02baee | ||
| 
						 | 
					7c8a75ce6c | ||
| 
						 | 
					cb0a925e27 | ||
| 
						 | 
					c7d2699f7e | ||
| 
						 | 
					b942b55193 | ||
| 
						 | 
					c1100fe983 | ||
| 
						 | 
					62236c90f5 | ||
| 
						 | 
					eabdab91fb | ||
| 
						 | 
					4f83076cfe | ||
| 
						 | 
					7eda1d420f | ||
| 
						 | 
					411388d62d | ||
| 
						 | 
					245d1317ac | ||
| 
						 | 
					ee5e34ca32 | ||
| 
						 | 
					c4dcef73e1 | ||
| 
						 | 
					4f53fee1fc | ||
| 
						 | 
					d163410e3d | ||
| 
						 | 
					9ca251edba | ||
| 
						 | 
					193cb9b30d | ||
| 
						 | 
					e9a36f67fe | ||
| 
						 | 
					c18a58caea | ||
| 
						 | 
					11659ab917 | ||
| 
						 | 
					89b1049809 | ||
| 
						 | 
					192c105719 | ||
| 
						 | 
					b8fc8efa07 | ||
| 
						 | 
					2e851cf88d | ||
| 
						 | 
					ebf081c731 | ||
| 
						 | 
					b979701264 | ||
| 
						 | 
					aa0d02abf2 | ||
| 
						 | 
					2f7b8d50a3 | ||
| 
						 | 
					800736a025 | ||
| 
						 | 
					b8b7e5193e | ||
| 
						 | 
					808faa71b3 | ||
| 
						 | 
					0bd476968b | ||
| 
						 | 
					e51e5e8ca5 | ||
| 
						 | 
					46db14d5ff | ||
| 
						 | 
					b5fa73ca3b | ||
| 
						 | 
					c962daae3b | ||
| 
						 | 
					8efc559b09 | ||
| 
						 | 
					08628732fc | ||
| 
						 | 
					7f74bf7b3e | ||
| 
						 | 
					390073b153 | ||
| 
						 | 
					68c87f69c5 | ||
| 
						 | 
					2b936050c8 | ||
| 
						 | 
					28184ca69a | ||
| 
						 | 
					e05c74813e | ||
| 
						 | 
					e05a1d9ba9 | ||
| 
						 | 
					5aab15a77a | ||
| 
						 | 
					5d267c8d60 | ||
| 
						 | 
					18fc9a58b4 | ||
| 
						 | 
					6a7ba876b7 | ||
| 
						 | 
					65bdb242ec | ||
| 
						 | 
					5a537b301a | ||
| 
						 | 
					515b894241 | ||
| 
						 | 
					4978d9e790 | 
@@ -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"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
{}
 | 
			
		||||
@@ -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)
 | 
			
		||||
{}
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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>
 | 
			
		||||
{}
 | 
			
		||||
@@ -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;
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -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) */
 | 
			
		||||
@@ -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());
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
{}
 | 
			
		||||
@@ -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)
 | 
			
		||||
{}
 | 
			
		||||
@@ -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)
 | 
			
		||||
{}
 | 
			
		||||
@@ -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)
 | 
			
		||||
{}
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -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"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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 "true TRUE, false FALSE"."/>
 | 
			
		||||
  <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."/>
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								pom.xml
									
									
									
									
									
								
							@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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.");
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -29,28 +29,17 @@ import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for objects capable of extracting an externally authenticated user ID from the HTTP Admin Console webscript request.
 | 
			
		||||
 * An interface for objects capable of extracting an externally authenticated user ID from the HTTP request.
 | 
			
		||||
 */
 | 
			
		||||
public interface AdminConsoleAuthenticator
 | 
			
		||||
public interface ExternalUserAuthenticator
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets an externally authenticated user ID from the HTTP Admin Console webscript request.
 | 
			
		||||
     * Gets an externally authenticated user ID from the HTTP request.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param request
 | 
			
		||||
     *            the request
 | 
			
		||||
     * @param response
 | 
			
		||||
     *            the response
 | 
			
		||||
     * @return the user ID or <code>null</code> if the user is unauthenticated
 | 
			
		||||
     */
 | 
			
		||||
    String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response);
 | 
			
		||||
    String getUserId(HttpServletRequest request, HttpServletResponse response);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Requests an authentication.
 | 
			
		||||
     *
 | 
			
		||||
     * @param request
 | 
			
		||||
     *            the request
 | 
			
		||||
     * @param response
 | 
			
		||||
     *            the response
 | 
			
		||||
     */
 | 
			
		||||
    /* Sends redirect to external site to initiate the OIDC authorization code flow. */
 | 
			
		||||
    void requestAuthentication(HttpServletRequest request, HttpServletResponse response);
 | 
			
		||||
}
 | 
			
		||||
@@ -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(","))
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
                                {
 | 
			
		||||
 
 | 
			
		||||
@@ -76,4 +76,17 @@ public interface UserRegistry
 | 
			
		||||
     * @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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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. -->
 | 
			
		||||
 
 | 
			
		||||
@@ -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" />
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -104,4 +104,7 @@
 | 
			
		||||
         <ref bean="transactionService" />
 | 
			
		||||
      </property>
 | 
			
		||||
   </bean>
 | 
			
		||||
 | 
			
		||||
    <bean id="webScriptsHomeAuthenticator" class="org.alfresco.repo.security.authentication.external.DefaultWebScriptsHomeAuthenticator" />
 | 
			
		||||
 | 
			
		||||
</beans>
 | 
			
		||||
@@ -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"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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());
 | 
			
		||||
@@ -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);
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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\")");
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@
 | 
			
		||||
             </onException>
 | 
			
		||||
             <transacted />
 | 
			
		||||
            <loadBalance>        
 | 
			
		||||
                <roundRobin/>
 | 
			
		||||
                <roundRobinLoadBalancer/>
 | 
			
		||||
                <to uri="bean:mockExceptionThrowingConsumer"/>
 | 
			
		||||
                <to uri="bean:mockConsumer"/>
 | 
			
		||||
            </loadBalance>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user