Compare commits

...

23 Commits

Author SHA1 Message Date
alfresco-build
17eef66f5c [maven-release-plugin][skip ci] prepare release 23.6.0.7 2025-07-23 08:47:08 +00:00
jakubkochman
1d1f269a70 PRODSEC-10304 bumped spring.version to 6.2.8 in 23.N (#3463) 2025-07-23 09:54:15 +02:00
alfresco-build
2ccf6044b8 [maven-release-plugin][skip ci] prepare for next development iteration 2025-07-21 04:28:24 +00:00
alfresco-build
bdd09784e1 [maven-release-plugin][skip ci] prepare release 23.6.0.6 2025-07-21 04:28:22 +00:00
bsayan2
de5d70be46 MNT-25150 check owner aspect first (#3479) 2025-07-18 11:54:26 +05:30
alfresco-build
8cacba0988 [maven-release-plugin][skip ci] prepare for next development iteration 2025-07-02 09:56:47 +00:00
alfresco-build
60187bf9a2 [maven-release-plugin][skip ci] prepare release 23.6.0.5 2025-07-02 09:56:45 +00:00
jakubkochman
ff4634be19 PRODSEC-10332 backport to 23.N (#3445) 2025-07-02 11:04:13 +02:00
alfresco-build
9c64b45908 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-24 06:43:06 +00:00
alfresco-build
d97d8fba04 [maven-release-plugin][skip ci] prepare release 23.6.0.4 2025-06-24 06:43:04 +00:00
KushalBanik
368b571d9c [MNT-10187] common-beanutils bumbed up to 1.11.0 (#3395) 2025-06-24 11:29:03 +05:30
alfresco-build
e9da7d222b [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-17 15:58:11 +00:00
alfresco-build
8c059460f9 [maven-release-plugin][skip ci] prepare release 23.6.0.3 2025-06-17 15:58:09 +00:00
jakubkochman
bd0aaa08b3 ACS-6928 backported to 23.N (#3390) 2025-06-17 17:12:37 +02:00
alfresco-build
93d678dc30 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-05 11:35:36 +00:00
alfresco-build
9648189827 [maven-release-plugin][skip ci] prepare release 23.6.0.2 2025-06-05 11:35:34 +00:00
SatyamSah5
9bfd274127 [ACS-9697] Backport to 23.N (#3375) 2025-06-05 16:23:23 +05:30
jakubkochman
dcf9f65f6b ACS-9646 backport to 23.N (#3378) 2025-06-05 12:12:22 +02:00
alfresco-build
784fae5834 [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-03 12:58:29 +00:00
alfresco-build
eddd8a1065 [maven-release-plugin][skip ci] prepare release 23.6.0.1 2025-06-03 12:58:26 +00:00
cezary-witkowski
0941746518 [MNT-24859] Basic Auth still possible with Keycloak enabled (#3373)
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com>
2025-06-03 14:14:49 +02:00
Damian Ujma
a1f0f35f60 Updating 23.N branch to 23.6.0 after 23.5.0 ACS release [skip ci] 2025-06-03 13:18:18 +02:00
alfresco-build
25d96a50cd [maven-release-plugin][skip ci] prepare for next development iteration 2025-06-02 08:05:26 +00:00
59 changed files with 7949 additions and 7382 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo-tests</artifactId>
<version>23.5.0.19</version>
<version>23.6.0.7</version>
</parent>
<organization>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

18
pom.xml
View File

@@ -2,7 +2,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>alfresco-community-repo</artifactId>
<version>23.5.0.19</version>
<version>23.6.0.7</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -24,7 +24,7 @@
<properties>
<acs.version.major>23</acs.version.major>
<acs.version.minor>5</acs.version.minor>
<acs.version.minor>6</acs.version.minor>
<acs.version.revision>0</acs.version.revision>
<acs.version.label />
<amp.min.version>${acs.version.major}.0.0</amp.min.version>
@@ -57,13 +57,13 @@
<dependency.acs-event-model.version>0.0.33</dependency.acs-event-model.version>
<dependency.aspectj.version>1.9.22.1</dependency.aspectj.version>
<dependency.spring.version>6.1.14</dependency.spring.version>
<dependency.spring-security.version>6.3.8</dependency.spring-security.version>
<dependency.spring.version>6.2.8</dependency.spring.version>
<dependency.spring-security.version>6.3.9</dependency.spring-security.version>
<dependency.antlr.version>3.5.3</dependency.antlr.version>
<dependency.jackson.version>2.17.2</dependency.jackson.version>
<dependency.cxf.version>4.1.0</dependency.cxf.version>
<dependency.opencmis.version>1.0.0-jakarta-1</dependency.opencmis.version>
<dependency.webscripts.version>9.4</dependency.webscripts.version>
<dependency.webscripts.version>10.2</dependency.webscripts.version>
<dependency.bouncycastle.version>1.78.1</dependency.bouncycastle.version>
<dependency.mockito-core.version>5.14.1</dependency.mockito-core.version>
<dependency.assertj.version>3.26.3</dependency.assertj.version>
@@ -154,7 +154,7 @@
<connection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</connection>
<developerConnection>scm:git:https://github.com/Alfresco/alfresco-community-repo.git</developerConnection>
<url>https://github.com/Alfresco/alfresco-community-repo</url>
<tag>23.5.0.19</tag>
<tag>23.6.0.7</tag>
</scm>
<distributionManagement>
@@ -409,7 +409,7 @@
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
@@ -439,8 +439,8 @@
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta</artifactId>
<version>2.0.0-M1</version>
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
<version>2.0.0-M4</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>

View File

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

View File

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

View File

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

View File

@@ -27,15 +27,11 @@
package org.alfresco.repo.web.scripts.transfer;
import java.io.OutputStream;
import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.repo.transfer.TransferCommons;
import org.alfresco.service.cmr.transfer.TransferException;
import org.alfresco.service.cmr.transfer.TransferReceiver;
import org.apache.commons.fileupload2.core.FileItemInput;
import org.apache.commons.fileupload2.core.FileItemInputIterator;
import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletFileUpload;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.Status;
@@ -44,6 +40,10 @@ import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.extensions.webscripts.WrappingWebScriptRequest;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.alfresco.repo.transfer.TransferCommons;
import org.alfresco.service.cmr.transfer.TransferException;
import org.alfresco.service.cmr.transfer.TransferReceiver;
/**
* This command processor is used to receive the snapshot for a given transfer.
*
@@ -59,8 +59,8 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
private static final String MSG_CAUGHT_UNEXPECTED_EXCEPTION = "transfer_service.receiver.caught_unexpected_exception";
/* (non-Javadoc)
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse)
*/
*
* @see org.alfresco.repo.web.scripts.transfer.CommandProcessor#process(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */
public int process(WebScriptRequest req, WebScriptResponse resp)
{
@@ -83,8 +83,7 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
{
current = null;
}
}
while (current != null);
} while (current != null);
if (webScriptServletRequest == null)
{
logger.debug("bad request, not assignable from");
@@ -92,11 +91,11 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
return Status.STATUS_BAD_REQUEST;
}
//We can't use the WebScriptRequest version of getParameter, since that may cause the content stream
//to be parsed. Get hold of the raw HttpServletRequest and work with that.
// We can't use the WebScriptRequest version of getParameter, since that may cause the content stream
// to be parsed. Get hold of the raw HttpServletRequest and work with that.
HttpServletRequest servletRequest = webScriptServletRequest.getHttpServletRequest();
//Read the transfer id from the request
// Read the transfer id from the request
String transferId = servletRequest.getParameter("transferId");
if ((transferId == null) || !JakartaServletFileUpload.isMultipartContent(servletRequest))
@@ -136,7 +135,7 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
catch (Exception ex)
{
logger.debug("exception caught", ex);
if(transferId != null)
if (transferId != null)
{
logger.debug("ending transfer", ex);
receiver.end(transferId);
@@ -151,7 +150,8 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
}
/**
* @param receiver the receiver to set
* @param receiver
* the receiver to set
*/
public void setReceiver(TransferReceiver receiver)
{

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -38,11 +38,19 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.surf.util.I18NUtil;
import org.alfresco.model.ContentModel;
import org.alfresco.query.PagingRequest;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.People;
@@ -77,8 +85,6 @@ import org.alfresco.util.AlfrescoCollator;
import org.alfresco.util.ISO9075;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.SearchLanguageConversion;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Queries implementation
@@ -88,7 +94,8 @@ import org.springframework.extensions.surf.util.I18NUtil;
*/
public class QueriesImpl implements Queries, InitializingBean
{
private final static Map<String,QName> NODE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
private static final Log LOGGER = LogFactory.getLog(QueriesImpl.class);
private final static Map<String, QName> NODE_SORT_PARAMS_TO_QNAMES = sortParamsToQNames(
PARAM_NAME, ContentModel.PROP_NAME,
PARAM_CREATEDAT, ContentModel.PROP_CREATED,
PARAM_MODIFIEDAT, ContentModel.PROP_MODIFIED);
@@ -104,23 +111,22 @@ public class QueriesImpl implements Queries, InitializingBean
PARAM_SITE_DESCRIPTION, ContentModel.PROP_DESCRIPTION);
/**
* Helper method to build a map of sort parameter names to QNames. This method iterates through
* the parameters. If a parameter is a String it is assumed to be a sort parameter name and will
* be followed by a QName to which it maps. If however it is a QName the local name of the OName
* is used as the sort parameter name.
* @param parameters to build up the map.
* Helper method to build a map of sort parameter names to QNames. This method iterates through the parameters. If a parameter is a String it is assumed to be a sort parameter name and will be followed by a QName to which it maps. If however it is a QName the local name of the OName is used as the sort parameter name.
*
* @param parameters
* to build up the map.
* @return the map
*/
private static Map<String, QName> sortParamsToQNames(Object... parameters)
{
Map<String, QName> map = new HashMap<>();
for (int i=0; i<parameters.length; i++)
for (int i = 0; i < parameters.length; i++)
{
map.put(
parameters[i] instanceof String
? (String)parameters[i++]
: ((QName)parameters[i]).getLocalName(),
(QName)parameters[i]);
? (String) parameters[i++]
: ((QName) parameters[i]).getLocalName(),
(QName) parameters[i]);
}
return Collections.unmodifiableMap(map);
}
@@ -173,8 +179,7 @@ public class QueriesImpl implements Queries, InitializingBean
public CollectionWithPagingInfo<Node> findNodes(Parameters parameters)
{
SearchService searchService = sr.getSearchService();
return new AbstractQuery<Node>(nodeService, searchService)
{
return new AbstractQuery<Node>(nodeService, searchService) {
private final Map<String, UserInfo> mapUserInfo = new HashMap<>(10);
@Override
@@ -205,7 +210,7 @@ public class QueriesImpl implements Queries, InitializingBean
QName filterNodeTypeQName = nodes.createQName(nodeTypeStr);
if (dictionaryService.getType(filterNodeTypeQName) == null)
{
throw new InvalidArgumentException("Unknown filter nodeType: "+nodeTypeStr);
throw new InvalidArgumentException("Unknown filter nodeType: " + nodeTypeStr);
}
query.append(" AND (+TYPE:\"").append(nodeTypeStr).append(("\")"));
@@ -248,7 +253,7 @@ public class QueriesImpl implements Queries, InitializingBean
{
// first request for this namespace prefix, get and cache result
Collection<String> prefixes = namespaceService.getPrefixes(qname.getNamespaceURI());
prefix = prefixes.size() != 0 ? prefixes.iterator().next() : "";
prefix = !prefixes.isEmpty() ? prefixes.iterator().next() : "";
cache.put(qname.getNamespaceURI(), prefix);
}
buf.append('/').append(prefix).append(':').append(ISO9075.encode(qname.getLocalName()));
@@ -262,12 +267,6 @@ public class QueriesImpl implements Queries, InitializingBean
return buf.toString();
}
@Override
protected List<Node> newList(int capacity)
{
return new ArrayList<Node>(capacity);
}
@Override
protected Node convert(NodeRef nodeRef, List<String> includeParam)
{
@@ -291,8 +290,7 @@ public class QueriesImpl implements Queries, InitializingBean
public CollectionWithPagingInfo<Person> findPeople(Parameters parameters)
{
SearchService searchService = sr.getSearchService();
return new AbstractQuery<Person>(nodeService, searchService)
{
return new AbstractQuery<Person>(nodeService, searchService) {
@Override
protected void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName)
{
@@ -305,18 +303,11 @@ public class QueriesImpl implements Queries, InitializingBean
query.append("*\")");
}
@Override
protected List<Person> newList(int capacity)
{
return new ArrayList<Person>(capacity);
}
@Override
protected Person convert(NodeRef nodeRef, List<String> includeParam)
{
String personId = (String) nodeService.getProperty(nodeRef, ContentModel.PROP_USERNAME);
Person person = people.getPerson(personId);
return person;
return people.getPerson(personId);
}
// TODO Do the sort in the query on day. A comment in the code for the V0 API used for live people
@@ -330,8 +321,7 @@ public class QueriesImpl implements Queries, InitializingBean
public CollectionWithPagingInfo<Site> findSites(Parameters parameters)
{
SearchService searchService = sr.getSearchService();
return new AbstractQuery<Site>(nodeService, searchService)
{
return new AbstractQuery<Site>(nodeService, searchService) {
@Override
protected void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName)
{
@@ -344,28 +334,18 @@ public class QueriesImpl implements Queries, InitializingBean
query.append("*\")");
}
@Override
protected List<Site> newList(int capacity)
{
return new ArrayList<>(capacity);
}
@Override
protected Site convert(NodeRef nodeRef, List<String> includeParam)
{
return getSite(siteService.getSite(nodeRef), true);
return getSite(siteService.getSite(nodeRef));
}
// note: see also Sites.getSite
private Site getSite(SiteInfo siteInfo, boolean includeRole)
private Site getSite(SiteInfo siteInfo)
{
// set the site id to the short name (to deal with case sensitivity issues with using the siteId from the url)
String siteId = siteInfo.getShortName();
String role = null;
if(includeRole)
{
role = sites.getSiteRole(siteId);
}
String role = sites.getSiteRole(siteId);
return new Site(siteInfo, role);
}
}.find(parameters, PARAM_TERM, MIN_TERM_LENGTH_SITES, "_SITE", POST_QUERY_SORT, SITE_SORT_PARAMS_TO_QNAMES, new SortColumn(PARAM_SITE_TITLE, true));
@@ -415,34 +395,41 @@ public class QueriesImpl implements Queries, InitializingBean
}
ResultSet queryResults = null;
List<T> collection = null;
try
{
queryResults = searchService.query(sp);
List<NodeRef> nodeRefs = queryResults.getNodeRefs();
if (sort == POST_QUERY_SORT)
{
nodeRefs = postQuerySort(parameters, sortParamsToQNames, defaultSortCols, nodeRefs);
}
Map<NodeRef, T>
collection = newList(nodeRefs.size());
collection = new LinkedHashMap<>(nodeRefs.size());
List<String> includeParam = parameters.getInclude();
for (NodeRef nodeRef : nodeRefs)
{
try
{
T t = convert(nodeRef, includeParam);
collection.add(t);
collection.put(nodeRef, t);
}
catch (AccessDeniedException ade)
{
LOGGER.debug("Ignoring search result for nodeRef " + nodeRef + " due to access denied exception", ade);
}
}
if (sort == POST_QUERY_SORT)
{
return listPage(collection, paging);
List<T> postQuerySortedCollection = postQuerySort(parameters, sortParamsToQNames, defaultSortCols, collection.keySet())
.stream()
.map(collection::get)
.toList();
return listPage(postQuerySortedCollection, paging);
}
else
{
return CollectionWithPagingInfo.asPaged(paging, collection, queryResults.hasMore(), Long.valueOf(queryResults.getNumberFound()).intValue());
return CollectionWithPagingInfo.asPaged(paging, collection.values(), queryResults.hasMore(), Long.valueOf(queryResults.getNumberFound()).intValue());
}
}
finally
@@ -455,26 +442,25 @@ public class QueriesImpl implements Queries, InitializingBean
}
/**
* Builds up the query and is expected to call {@link SearchParameters#setDefaultFieldName(String)}
* and {@link SearchParameters#addQueryTemplate(String, String)}
* @param query StringBuilder into which the query should be built.
* @param term to be searched for
* @param sp SearchParameters
* Builds up the query and is expected to call {@link SearchParameters#setDefaultFieldName(String)} and {@link SearchParameters#addQueryTemplate(String, String)}
*
* @param query
* StringBuilder into which the query should be built.
* @param term
* to be searched for
* @param sp
* SearchParameters
* @param queryTemplateName
*/
protected abstract void buildQuery(StringBuilder query, String term, SearchParameters sp, String queryTemplateName);
/**
* Returns a list of the correct type.
* @param capacity of the list
* @return a new list.
*/
protected abstract List<T> newList(int capacity);
/**
* Converts a nodeRef into the an object of the required type.
* @param nodeRef to be converted
* @param includeParam additional fields to be included
*
* @param nodeRef
* to be converted
* @param includeParam
* additional fields to be included
* @return the object
*/
protected abstract T convert(NodeRef nodeRef, List<String> includeParam);
@@ -484,7 +470,7 @@ public class QueriesImpl implements Queries, InitializingBean
String term = parameters.getParameter(termName);
if (term == null)
{
throw new InvalidArgumentException("Query '"+termName+"' not specified");
throw new InvalidArgumentException("Query '" + termName + "' not specified");
}
term = escapeTerm(term);
@@ -505,7 +491,7 @@ public class QueriesImpl implements Queries, InitializingBean
if (cnt < minTermLength)
{
throw new InvalidArgumentException("Query '"+termName+"' is too short. Must have at least "+minTermLength+" alphanumeric chars");
throw new InvalidArgumentException("Query '" + termName + "' is too short. Must have at least " + minTermLength + " alphanumeric chars");
}
return term;
@@ -537,7 +523,7 @@ public class QueriesImpl implements Queries, InitializingBean
QName sortPropQName = sortParamsToQNames.get(sortCol.column);
if (sortPropQName == null)
{
throw new InvalidArgumentException("Invalid sort field: "+sortCol.column);
throw new InvalidArgumentException("Invalid sort field: " + sortCol.column);
}
sp.addSort("@" + sortPropQName, sortCol.asc);
}
@@ -546,7 +532,7 @@ public class QueriesImpl implements Queries, InitializingBean
private List<SortColumn> getSorting(Parameters parameters, List<SortColumn> defaultSortCols)
{
List<SortColumn> sortCols = parameters.getSorting();
if (sortCols == null || sortCols.size() == 0)
if (sortCols == null || sortCols.isEmpty())
{
sortCols = defaultSortCols == null ? Collections.emptyList() : defaultSortCols;
}
@@ -554,14 +540,18 @@ public class QueriesImpl implements Queries, InitializingBean
}
protected List<NodeRef> postQuerySort(Parameters parameters, Map<String, QName> sortParamsToQNames,
List<SortColumn> defaultSortCols, List<NodeRef> nodeRefs)
List<SortColumn> defaultSortCols, Set<NodeRef> unsortedNodeRefs)
{
final List<SortColumn> sortCols = getSorting(parameters, defaultSortCols);
int sortColCount = sortCols.size();
if (sortColCount > 0)
if (sortColCount == 0)
{
return new ArrayList<>(unsortedNodeRefs);
}
// make copy of nodeRefs because it can be unmodifiable list.
nodeRefs = new ArrayList<NodeRef>(nodeRefs);
List<NodeRef> sortedNodeRefs = new ArrayList<>(unsortedNodeRefs);
List<QName> sortPropQNames = new ArrayList<>(sortColCount);
for (SortColumn sortCol : sortCols)
@@ -569,19 +559,18 @@ public class QueriesImpl implements Queries, InitializingBean
QName sortPropQName = sortParamsToQNames.get(sortCol.column);
if (sortPropQName == null)
{
throw new InvalidArgumentException("Invalid sort field: "+sortCol.column);
throw new InvalidArgumentException("Invalid sort field: " + sortCol.column);
}
sortPropQNames.add(sortPropQName);
}
final Collator col = AlfrescoCollator.getInstance(I18NUtil.getLocale());
Collections.sort(nodeRefs, new Comparator<NodeRef>()
{
Collections.sort(sortedNodeRefs, new Comparator<NodeRef>() {
@Override
public int compare(NodeRef n1, NodeRef n2)
{
int result = 0;
for (int i=0; i<sortCols.size(); i++)
for (int i = 0; i < sortCols.size(); i++)
{
SortColumn sortCol = sortCols.get(i);
QName sortPropQName = sortPropQNames.get(i);
@@ -590,7 +579,7 @@ public class QueriesImpl implements Queries, InitializingBean
Serializable p2 = getProperty(n2, sortPropQName);
result = ((p1 instanceof Long) && (p2 instanceof Long)
? Long.compare((Long)p1, (Long)p2)
? Long.compare((Long) p1, (Long) p2)
: col.compare(p1.toString(), p2.toString()))
* (sortCol.asc ? 1 : -1);
@@ -609,8 +598,8 @@ public class QueriesImpl implements Queries, InitializingBean
}
});
}
return nodeRefs;
return sortedNodeRefs;
}
// note: see also AbstractNodeRelation

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>23.5.0.19</version>
<version>23.6.0.7</version>
</parent>
<dependencies>
@@ -85,7 +85,7 @@
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-fileupload2-jakarta</artifactId>
<artifactId>commons-fileupload2-jakarta-servlet6</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2021 Alfresco Software Limited
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -30,6 +30,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.cache.lookup.EntityLookupCache;
import org.alfresco.repo.domain.node.Node;
@@ -41,8 +44,6 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.EqualsHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class NodePermissionAssessor
{
@@ -114,15 +115,15 @@ public class NodePermissionAssessor
Set<QName> nodeAspects = nodeService.getAspects(node.getNodeRef());
String userName = null;
if (nodeAspects.contains(ContentModel.ASPECT_AUDITABLE))
{
userName = node.getAuditableProperties().getAuditCreator();
}
else if (nodeAspects.contains(ContentModel.ASPECT_OWNABLE))
if (nodeAspects.contains(ContentModel.ASPECT_OWNABLE))
{
Serializable owner = nodeService.getProperty(node.getNodeRef(), ContentModel.PROP_OWNER);
userName = DefaultTypeConverter.INSTANCE.convert(String.class, owner);
}
else if (nodeAspects.contains(ContentModel.ASPECT_AUDITABLE))
{
userName = node.getAuditableProperties().getAuditCreator();
}
return userName;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.identityservice.admin;
package org.alfresco.repo.security.authentication.identityservice.authentication;
import static java.util.Arrays.asList;
import static java.util.Collections.enumeration;
@@ -37,18 +37,12 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.alfresco.util.PropertyCheck;
public class AdminConsoleHttpServletRequestWrapper extends HttpServletRequestWrapper
public class AdditionalHeadersHttpServletRequestWrapper extends HttpServletRequestWrapper
{
private final Map<String, String> additionalHeaders;
private final HttpServletRequest wrappedRequest;
/**
* Constructs a request object wrapping the given request.
*
* @param request the request to wrap
* @throws IllegalArgumentException if the request is null
*/
public AdminConsoleHttpServletRequestWrapper(Map<String, String> additionalHeaders, HttpServletRequest request)
public AdditionalHeadersHttpServletRequestWrapper(Map<String, String> additionalHeaders, HttpServletRequest request)
{
super(request);
PropertyCheck.mandatory(this, "additionalHeaders", additionalHeaders);

View File

@@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.identityservice.admin;
package org.alfresco.repo.security.authentication.identityservice.authentication;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
@@ -33,12 +33,12 @@ import org.alfresco.repo.admin.SysAdminParams;
/**
* Service to handle Admin Console authentication-related cookies.
*/
public class AdminConsoleAuthenticationCookiesService
public class AdminAuthenticationCookiesService
{
private final SysAdminParams sysAdminParams;
private final int cookieLifetime;
public AdminConsoleAuthenticationCookiesService(SysAdminParams sysAdminParams, int cookieLifetime)
public AdminAuthenticationCookiesService(SysAdminParams sysAdminParams, int cookieLifetime)
{
this.sysAdminParams = sysAdminParams;
this.cookieLifetime = cookieLifetime;

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -28,9 +28,6 @@ package org.alfresco.repo.transaction;
import java.lang.reflect.Method;
import java.util.List;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.transaction.TransactionService;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.transaction.PlatformTransactionManager;
@@ -38,6 +35,10 @@ import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.transaction.TransactionService;
/**
* @author Dmitry Velichkevich
*/
@@ -64,7 +65,7 @@ public class RetryingTransactionInterceptor extends TransactionAspectSupport imp
final TransactionAttribute txnAttr = getTransactionAttributeSource().getTransactionAttribute(
method, invocation.getThis().getClass());
final TransactionManager tm = determineTransactionManager(txnAttr);
final TransactionManager tm = determineTransactionManager(txnAttr, null);
if (tm != null && !(tm instanceof PlatformTransactionManager))
{
@@ -83,8 +84,7 @@ public class RetryingTransactionInterceptor extends TransactionAspectSupport imp
retryingTransactionHelper.setExtraExceptions(extraExceptions);
return retryingTransactionHelper.doInTransaction(
new RetryingTransactionCallback<Object>()
{
new RetryingTransactionCallback<Object>() {
public Object execute()
{
TransactionInfo txInfo = createTransactionIfNecessary(ptm, TransactionAttribute.PROPAGATION_REQUIRES_NEW == txnAttr

View File

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

View File

@@ -3,7 +3,7 @@
repository.name=Main Repository
# Schema number
version.schema=19400
version.schema=19500
# Directory configuration
@@ -563,6 +563,7 @@ authentication.ticket.validDuration=PT1H
authentication.ticket.useSingleTicketPerUser=true
authentication.alwaysAllowBasicAuthForAdminConsole.enabled=true
authentication.alwaysAllowBasicAuthForWebScriptsHome.enabled=true
authentication.getRemoteUserTimeoutMilliseconds=10000
# FTP access

View File

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

View File

@@ -170,6 +170,9 @@
<property name="adminConsoleScopes">
<value>${identity-service.admin-console.scopes:openid,profile,email,offline_access}</value>
</property>
<property name="webScriptsHomeScopes">
<value>${identity-service.webscripts-home.scopes:openid,profile,email,offline_access}</value>
</property>
<property name="passwordGrantScopes">
<value>${identity-service.password-grant.scopes:openid,profile,email}</value>
</property>
@@ -179,6 +182,9 @@
<property name="jwtClockSkewMs">
<value>${identity-service.jwt-clock-skew-ms:0}</value>
</property>
<property name="webScriptsHomeRedirectPath">
<value>${identity-service.webscripts-home.redirect-path}</value>
</property>
</bean>
<!-- Enable control over mapping between request and user ID -->
@@ -197,12 +203,12 @@
</property>
</bean>
<bean id="adminConsoleAuthenticationCookiesService" class="org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleAuthenticationCookiesService">
<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,25 @@
<ref bean="identityServiceFacade"/>
</property>
<property name="cookiesService">
<ref bean="adminConsoleAuthenticationCookiesService" />
<ref bean="adminAuthenticationCookiesService" />
</property>
<property name="remoteUserMapper">
<ref bean="remoteUserMapper" />
</property>
<property name="identityServiceConfig">
<ref bean="identityServiceConfig" />
</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" />

View File

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

View File

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

View File

@@ -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;
@@ -40,51 +40,45 @@ import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import org.alfresco.error.AlfrescoRuntimeException;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@SuppressWarnings("PMD.UseDiamondOperator")
public class AdminConsoleHttpServletRequestWrapperUnitTest
{
import org.alfresco.error.AlfrescoRuntimeException;
@SuppressWarnings("PMD.UseDiamondOperator")
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>()
{{
put(DEFAULT_HEADER, DEFAULT_HEADER_VALUE);
}};
private static final Map<String, String> ADDITIONAL_HEADERS = new HashMap<String, String>()
{{
put(ADDITIONAL_HEADER, ADDITIONAL_HEADER_VALUE);
}};
private static final Map<String, String> DEFAULT_HEADERS = Map.of(DEFAULT_HEADER, DEFAULT_HEADER_VALUE);
private static final Map<String, String> ADDITIONAL_HEADERS = Map.of(ADDITIONAL_HEADER, ADDITIONAL_HEADER_VALUE);
@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
@@ -109,7 +103,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());
@@ -161,7 +155,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);
@@ -201,7 +195,7 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
Map<String, String> overrideHeaders = new HashMap<>();
overrideHeaders.put(DEFAULT_HEADER, overrideHeaderValue);
requestWrapper = new AdminConsoleHttpServletRequestWrapper(overrideHeaders, request);
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(overrideHeaders, request);
Enumeration<String> headers = requestWrapper.getHeaders(DEFAULT_HEADER);
assertNotNull("The headers enumeration should not be null", headers);
assertTrue("The headers enumeration should not be empty", headers.hasMoreElements());

View File

@@ -23,7 +23,7 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.identityservice.admin;
package org.alfresco.repo.security.authentication.identityservice.authentication;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -37,14 +37,16 @@ import static org.mockito.MockitoAnnotations.initMocks;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.alfresco.repo.admin.SysAdminParams;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
public class AdminConsoleAuthenticationCookiesServiceUnitTest
import org.alfresco.repo.admin.SysAdminParams;
public class AdminAuthenticationCookiesServiceUnitTest
{
private static final int DEFAULT_COOKIE_LIFETIME = 86400;
private static final String COOKIE_NAME = "cookie";
@@ -57,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
@@ -136,7 +138,7 @@ public class AdminConsoleAuthenticationCookiesServiceUnitTest
public void cookieWithCustomMaxAgeShouldBeAddedToTheResponse()
{
int customMaxAge = 60;
cookiesService = new AdminConsoleAuthenticationCookiesService(sysAdminParams, customMaxAge);
cookiesService = new AdminAuthenticationCookiesService(sysAdminParams, customMaxAge);
when(sysAdminParams.getAlfrescoProtocol()).thenReturn("https");
cookiesService.addCookie(COOKIE_NAME, COOKIE_VALUE, response);

View File

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

View File

@@ -0,0 +1,253 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2025 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.security.authentication.identityservice.webscript;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import java.io.IOException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.nimbusds.oauth2.sdk.Scope;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessToken;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
import org.alfresco.repo.security.authentication.identityservice.authentication.AdditionalHeadersHttpServletRequestWrapper;
import org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesService;
import org.alfresco.repo.security.authentication.identityservice.authentication.webscripts.IdentityServiceWebScriptsHomeAuthenticator;
@SuppressWarnings("PMD.AvoidStringBufferField")
public class IdentityServiceWebScriptsHomeAuthenticatorUnitTest
{
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
@Mock
HttpServletRequest request;
@Mock
HttpServletResponse response;
@Mock
IdentityServiceFacade identityServiceFacade;
@Mock
IdentityServiceConfig identityServiceConfig;
@Mock
AdminAuthenticationCookiesService cookiesService;
@Mock
RemoteUserMapper remoteUserMapper;
@Mock
AccessTokenAuthorization accessTokenAuthorization;
@Mock
AccessToken accessToken;
@Captor
ArgumentCaptor<AdditionalHeadersHttpServletRequestWrapper> requestCaptor;
IdentityServiceWebScriptsHomeAuthenticator authenticator;
StringBuffer webScriptHomeURL = new StringBuffer("http://localhost:8080/alfresco/s/index");
@Before
public void setup()
{
initMocks(this);
ClientRegistration clientRegistration = mock(ClientRegistration.class);
ProviderDetails providerDetails = mock(ProviderDetails.class);
Scope scope = Scope.parse(Arrays.asList("openid", "profile", "email", "offline_access"));
when(clientRegistration.getProviderDetails()).thenReturn(providerDetails);
when(clientRegistration.getClientId()).thenReturn("alfresco");
when(providerDetails.getAuthorizationUri()).thenReturn("http://localhost:8999/auth");
when(providerDetails.getConfigurationMetadata()).thenReturn(Map.of("scopes_supported", scope));
when(identityServiceFacade.getClientRegistration()).thenReturn(clientRegistration);
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
when(remoteUserMapper.getRemoteUser(request)).thenReturn(null);
authenticator = new IdentityServiceWebScriptsHomeAuthenticator();
authenticator.setActive(true);
authenticator.setIdentityServiceFacade(identityServiceFacade);
authenticator.setCookiesService(cookiesService);
authenticator.setRemoteUserMapper(remoteUserMapper);
authenticator.setIdentityServiceConfig(identityServiceConfig);
}
@Test
public void shouldCallRemoteMapperIfTokenIsInCookies()
{
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().plusSeconds(60).toEpochMilli()));
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getUserId(request, response);
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
assertEquals("admin", username);
assertTrue(authenticator.isActive());
}
@Test
public void shouldRefreshExpiredTokenAndCallRemoteMapper()
{
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().minusSeconds(60).toEpochMilli()));
when(accessToken.getTokenValue()).thenReturn("REFRESHED_JWT_TOKEN");
when(accessToken.getExpiresAt()).thenReturn(Instant.now().plusSeconds(60));
when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken);
when(accessTokenAuthorization.getRefreshTokenValue()).thenReturn("REFRESH_TOKEN");
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenReturn(accessTokenAuthorization);
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getUserId(request, response);
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "REFRESHED_JWT_TOKEN", response);
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
assertEquals("Bearer REFRESHED_JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
assertEquals("admin", username);
}
@Test
public void shouldCallAuthChallengeWebScriptHome() throws IOException
{
String redirectPath = "/alfresco/s/index";
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
when(identityServiceConfig.getWebScriptsHomeScopes()).thenReturn(Set.of("openid", "email", "profile", "offline_access"));
when(identityServiceConfig.getWebScriptsHomeRedirectPath()).thenReturn(redirectPath);
ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class);
String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope="
.formatted("http://localhost:8080", redirectPath);
authenticator.requestAuthentication(request, response);
verify(response).sendRedirect(authenticationRequest.capture());
assertTrue(authenticationRequest.getValue().contains(expectedUri));
assertTrue(authenticationRequest.getValue().contains("openid"));
assertTrue(authenticationRequest.getValue().contains("profile"));
assertTrue(authenticationRequest.getValue().contains("email"));
assertTrue(authenticationRequest.getValue().contains("offline_access"));
assertTrue(authenticationRequest.getValue().contains("state"));
}
@Test
public void shouldCallAuthChallengeWebScriptHomeWithAudience() throws IOException
{
String audience = "http://localhost:8082";
String redirectPath = "/alfresco/s/index";
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
when(identityServiceConfig.getAudience()).thenReturn(audience);
when(identityServiceConfig.getWebScriptsHomeRedirectPath()).thenReturn(redirectPath);
when(identityServiceConfig.getWebScriptsHomeScopes()).thenReturn(Set.of("openid", "email", "profile", "offline_access"));
ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class);
String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope="
.formatted("http://localhost:8080", redirectPath);
authenticator.requestAuthentication(request, response);
verify(response).sendRedirect(authenticationRequest.capture());
assertTrue(authenticationRequest.getValue().contains(expectedUri));
assertTrue(authenticationRequest.getValue().contains("openid"));
assertTrue(authenticationRequest.getValue().contains("profile"));
assertTrue(authenticationRequest.getValue().contains("email"));
assertTrue(authenticationRequest.getValue().contains("offline_access"));
assertTrue(authenticationRequest.getValue().contains("audience=%s".formatted(audience)));
assertTrue(authenticationRequest.getValue().contains("state"));
}
@Test
public void shouldResetCookiesAndCallAuthChallenge() throws IOException
{
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN");
when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN");
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
String.valueOf(Instant.now().minusSeconds(60).toEpochMilli()));
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenThrow(AuthorizationException.class);
String username = authenticator.getUserId(request, response);
verify(cookiesService).resetCookie(ALFRESCO_ACCESS_TOKEN, response);
verify(cookiesService).resetCookie(ALFRESCO_REFRESH_TOKEN, response);
verify(cookiesService).resetCookie(ALFRESCO_TOKEN_EXPIRATION, response);
assertNull(username);
}
@Test
public void shouldAuthorizeCodeAndSetCookies()
{
when(request.getParameter("code")).thenReturn("auth_code");
when(accessToken.getTokenValue()).thenReturn("JWT_TOKEN");
when(accessToken.getExpiresAt()).thenReturn(Instant.now().plusSeconds(60));
when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken);
when(accessTokenAuthorization.getRefreshTokenValue()).thenReturn("REFRESH_TOKEN");
when(identityServiceFacade.authorize(
AuthorizationGrant.authorizationCode("auth_code", webScriptHomeURL.toString())))
.thenReturn(accessTokenAuthorization);
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
String username = authenticator.getUserId(request, response);
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "JWT_TOKEN", response);
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
assertEquals("admin", username);
}
@Test
public void shouldExtractUsernameFromAuthorizationHeader()
{
when(remoteUserMapper.getRemoteUser(request)).thenReturn("admin");
String username = authenticator.getUserId(request, response);
assertEquals("admin", username);
}
}

View File

@@ -25,17 +25,25 @@
*/
package org.alfresco.repo.site;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
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;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
@@ -44,6 +52,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;
@@ -53,6 +63,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;
@@ -66,21 +77,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;
@@ -90,22 +90,10 @@ import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.BaseSpringTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.BaseAlfrescoSpringTest;
import org.alfresco.util.GUID;
import org.alfresco.util.testing.category.LuceneTests;
import org.alfresco.util.testing.category.RedundantTests;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.springframework.extensions.surf.util.I18NUtil;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
/**
* Site service implementation unit test
@@ -172,8 +160,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
public void before() throws Exception
{
super.before();
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -252,7 +239,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
return null;
}
};
transactionService = (TransactionService)this.applicationContext.getBean("transactionComponent");
transactionService = (TransactionService) this.applicationContext.getBean("transactionComponent");
transactionService.getRetryingTransactionHelper().doInTransaction(work, false, true);
}
@@ -266,8 +253,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* This test method ensures that public sites can be created and that their site info is correct.
* It also tests that a duplicate site cannot be created.
* This test method ensures that public sites can be created and that their site info is correct. It also tests that a duplicate site cannot be created.
*/
@Test
public void testCreateSite() throws Exception
@@ -278,7 +264,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
checkSiteInfo(siteInfo, TEST_SITE_PRESET, mySiteTest, TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC);
String name = "!£$%^&*()_+=-[]{}";
//Calls deprecated method (still creates a public Site)
// Calls deprecated method (still creates a public Site)
siteInfo = this.siteService.createSite(TEST_SITE_PRESET, name, TEST_TITLE, TEST_DESCRIPTION, true);
checkSiteInfo(siteInfo, TEST_SITE_PRESET, name, TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC);
siteInfo = this.siteService.getSite(name);
@@ -323,7 +309,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
try
{
//Create a site with an invalid site type
// Create a site with an invalid site type
this.siteService.createSite(TEST_SITE_PRESET, "InvalidSiteType", TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC, ServiceRegistry.CMIS_SERVICE);
fail("Shouldn't allow invalid site type.");
}
@@ -336,8 +322,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testHasSite() throws Exception
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -375,8 +360,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testETHREEOH_2133() throws Exception
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -407,10 +391,12 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* This method tests https://issues.alfresco.com/jira/browse/ALF-3785 which allows 'public' sites
* to be only visible to members of a configured group, by default EVERYONE.
* This method tests https://issues.alfresco.com/jira/browse/ALF-3785 which allows 'public' sites to be only visible to members of a configured group, by default EVERYONE.
*
* <br>
* <br/>
* author Neil McErlean
*
* <br><br/>author Neil McErlean
* @since 3.4
*/
@SuppressWarnings("deprecation")
@@ -436,7 +422,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC);
assertTrue(containsConsumerPermission(originalSitePublicGroup, unconfiguredSite));
// Now set the managed bean's visibility group to something other than GROUP_EVERYONE.
// This is the group that will have visibility of subsequently created sites.
//
@@ -464,11 +449,9 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
fail("Expected exception on createSite with non-existent group was not thrown.");
}
// Now we'll create the group used above.
authorityService.createAuthority(AuthorityType.GROUP, newGroupName);
// And create the site as before. This time it should succeed.
SiteInfo configuredSite = siteService.createSite(TEST_SITE_PRESET, "configured",
TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PUBLIC);
@@ -510,8 +493,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testETHREEOH_15() throws Exception
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -540,16 +522,14 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
fail("Shouldn't be able to do this cos you don't have permissions");
}
catch (Exception exception)
{
}
{}
try
{
siteService.removeMembership(siteInfo.getShortName(), USER_ONE);
fail("Shouldn't be able to do this cos you don't have permissions");
}
catch (Exception exception)
{
}
{}
siteService.removeMembership(siteInfo.getShortName(), USER_THREE);
authenticationComponent.setSystemUserAsCurrentUser();
@@ -583,10 +563,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
/**
* Test listSite and findSites methods.
* <p/>
* Note that {@link SiteService#findSites(String, int)} offers eventually consistent results and therefore may
* exhibit changed behaviour if Lucene is switched off or is replaced by SOLR.
* {@link SiteService#listSites(List, List, org.alfresco.query.PagingRequest)} and the other listSites methods
* should offer consistent, accurate result sets.
* Note that {@link SiteService#findSites(String, int)} offers eventually consistent results and therefore may exhibit changed behaviour if Lucene is switched off or is replaced by SOLR. {@link SiteService#listSites(List, List, org.alfresco.query.PagingRequest)} and the other listSites methods should offer consistent, accurate result sets.
*/
@Category(RedundantTests.class)
@Test
@@ -687,7 +664,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
}
//Test the public method on the implementation.
// Test the public method on the implementation.
Set<String> sitesSet = new HashSet<>(2);
sitesSet.add("mySiteOne");
sitesSet.add("mySiteTwo");
@@ -812,8 +789,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testMNT_13710() throws Exception
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -884,7 +860,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
}
/**
* This test method ensures that searches with wildcards work as they should
*/
@@ -936,11 +911,11 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
this.siteService.createSite(TEST_SITE_PRESET, "siteLiveB", "getafix1vitalstatistix", TEST_DESCRIPTION, SiteVisibility.PUBLIC);
this.siteService.createSite(TEST_SITE_PRESET, "siteLiveC", "Armorican Gaul France", TEST_DESCRIPTION, SiteVisibility.PUBLIC);
//ACE-1428
// ACE-1428
this.siteService.createSite(TEST_SITE_PRESET, "siteLiveD", "n3w s1t3 creat3ed 88", TEST_DESCRIPTION, SiteVisibility.PUBLIC);
this.siteService.createSite(TEST_SITE_PRESET, "siteLiveE", "n3w s1t3 creat3ed 99", TEST_DESCRIPTION, SiteVisibility.PUBLIC);
//More scenarios
// More scenarios
this.siteService.createSite(TEST_SITE_PRESET, "siteLiveF", "super exciting product", TEST_DESCRIPTION, SiteVisibility.PUBLIC);
this.siteService.createSite(TEST_SITE_PRESET, "siteLiveG", "super exciting launch", TEST_DESCRIPTION, SiteVisibility.PUBLIC);
this.siteService.createSite(TEST_SITE_PRESET, "siteLiveH", "amazing sales 54", TEST_DESCRIPTION, SiteVisibility.PUBLIC);
@@ -1186,7 +1161,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testMoveFolderStructureWithNonInheritedPermission()
{
//Login to share as the admin user
// Login to share as the admin user
AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
// Create 2 sites test1, test2 as admin
@@ -1253,16 +1228,16 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
// This is what Share does:
// move the node
//result.success = fileNode.move(parent, destNode);
// result.success = fileNode.move(parent, destNode);
//
//if (result.success)
//{
// if (result.success)
// {
// // If this was an inter-site move, we'll need to clean up the permissions on the node
// if ((fromSite) && (String(fromSite) !== String(fileNode.siteShortName)))
// {
// siteService.cleanSitePermissions(fileNode);
// }
//}
// }
siteService.cleanSitePermissions(fol1, test2SiteInfo);
childAssocs = nodeService.getChildAssocs(documentLibraryTest1Site);
@@ -1293,12 +1268,11 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
public void testDeleteSite()
{
@SuppressWarnings("deprecation")
SiteService smallSiteService = (SiteService)this.applicationContext.getBean("siteService");
SiteService smallSiteService = (SiteService) this.applicationContext.getBean("siteService");
// Create a test group
final String testGroupName = "siteServiceImplTestGroup_" + GUID.generate();
String testGroup = AuthenticationUtil.runAs(
new AuthenticationUtil.RunAsWork<String>()
{
new AuthenticationUtil.RunAsWork<String>() {
public String doWork() throws Exception
{
return authorityService.createAuthority(AuthorityType.GROUP, testGroupName);
@@ -1322,12 +1296,12 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
// related site groups should remain after site delete but should be deleted on site purge from trashcan.
// Such case is tested in SiteServiceImplMoreTest.deleteSiteAndRestoreEnsuringSiteGroupsAreRecovered
assertTrue(authorityService.authorityExists(((SiteServiceImpl)smallSiteService).getSiteGroup(siteShortName, true)));
assertTrue(authorityService.authorityExists(((SiteServiceImpl) smallSiteService).getSiteGroup(siteShortName, true)));
assertTrue(authorityService.authorityExists(((SiteServiceImpl) smallSiteService).getSiteGroup(siteShortName)));
Set<String> permissions = permissionService.getSettablePermissions(SiteModel.TYPE_SITE);
for (String permission : permissions)
{
String siteRoleGroup = ((SiteServiceImpl)smallSiteService).getSiteRoleGroup(siteShortName, permission, true);
String siteRoleGroup = ((SiteServiceImpl) smallSiteService).getSiteRoleGroup(siteShortName, permission, true);
assertTrue(authorityService.authorityExists(siteRoleGroup));
}
@@ -1338,8 +1312,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testIsPublic()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -1391,8 +1364,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testMembership()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -1451,9 +1423,8 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertTrue(members.containsKey(USER_THREE));
assertEquals(SiteModel.SITE_COLLABORATOR, members.get(USER_THREE));
//Check other listMember calls
siteService.listMembers(siteShortName, null, null, false, new SiteService.SiteMembersCallback()
{
// Check other listMember calls
siteService.listMembers(siteShortName, null, null, false, new SiteService.SiteMembersCallback() {
List<String> USERS = Arrays.asList(USER_ONE, USER_TWO, USER_THREE);
int userCount = 0;
@@ -1469,7 +1440,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Override
public boolean isDone()
{
return userCount==USERS.size();
return userCount == USERS.size();
}
});
@@ -1580,8 +1551,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testListSiteMemberships()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -1669,13 +1639,10 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
transactionService.getRetryingTransactionHelper().doInTransaction(work);
}
@Test
public void testJoinLeave()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -1869,8 +1836,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* Creates a site with a custom type, and ensures that
* it behaves correctly.
* Creates a site with a custom type, and ensures that it behaves correctly.
*/
@SuppressWarnings("deprecation")
@Test
@@ -1880,31 +1846,27 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
final String CS_PFX = "cs";
// Setup our custom site type
DictionaryDAO dictionaryDAO = (DictionaryDAO)this.applicationContext.getBean("dictionaryDAO");
DictionaryDAO dictionaryDAO = (DictionaryDAO) this.applicationContext.getBean("dictionaryDAO");
M2Model model = M2Model.createModel("cm:CustomSiteModel");
model.createNamespace(CS_URI, CS_PFX);
// Import the usual suspects too
model.createImport(
NamespaceService.CONTENT_MODEL_1_0_URI,
NamespaceService.CONTENT_MODEL_PREFIX
);
NamespaceService.CONTENT_MODEL_PREFIX);
model.createImport(
NamespaceService.DICTIONARY_MODEL_1_0_URI,
NamespaceService.DICTIONARY_MODEL_PREFIX
);
NamespaceService.DICTIONARY_MODEL_PREFIX);
model.createImport(
SiteModel.SITE_MODEL_URL,
SiteModel.SITE_MODEL_PREFIX
);
SiteModel.SITE_MODEL_PREFIX);
// Custom type
M2Type customType = model.createType("cs:customSite");
customType.setTitle("customSite");
customType.setParentName(
SiteModel.SITE_MODEL_PREFIX + ":" +
SiteModel.TYPE_SITE.getLocalName()
);
SiteModel.TYPE_SITE.getLocalName());
M2Property customProp = customType.createProperty("cs:customSiteProp");
customProp.setTitle("customSiteProp");
@@ -1919,8 +1881,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
// Create a site
SiteInfo site = siteService.createSite(
"custom", "custom", "Custom", "Custom",
SiteVisibility.PUBLIC
);
SiteVisibility.PUBLIC);
// Check the roles on it
List<String> roles = siteService.getSiteRoles();
@@ -1937,7 +1898,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR));
assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER));
// Swap the type
nodeService.setType(site.getNodeRef(), customTypeQ);
@@ -1956,20 +1916,16 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertEquals(true, roles.contains(SiteServiceImpl.SITE_COLLABORATOR));
assertEquals(true, roles.contains(SiteServiceImpl.SITE_MANAGER));
// Alter the permissions for the custom site
PermissionService testPermissionService = spy(
(PermissionService)this.applicationContext.getBean("permissionServiceImpl")
);
(PermissionService) this.applicationContext.getBean("permissionServiceImpl"));
Set<String> customPerms = new HashSet<String>();
customPerms.add(SiteServiceImpl.SITE_MANAGER);
customPerms.add("CUSTOM");
when(testPermissionService.getSettablePermissions(customTypeQ)).
thenReturn(customPerms);
when(testPermissionService.getSettablePermissions(customTypeQ)).thenReturn(customPerms);
// Check it changed for the custom site, but not normal
SiteServiceImpl siteServiceImpl = (SiteServiceImpl)
this.applicationContext.getBean("siteService");
SiteServiceImpl siteServiceImpl = (SiteServiceImpl) this.applicationContext.getBean("siteService");
siteServiceImpl.setPermissionService(testPermissionService);
roles = siteService.getSiteRoles();
@@ -2004,8 +1960,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testGroupMembership()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
{
@@ -2070,7 +2025,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertEquals(SiteModel.SITE_CONSUMER, siteService.getMembersRole(testGroupMembership, USER_THREE));
assertEquals(SiteModel.SITE_CONSUMER, siteService.getMembersRole(testGroupMembership, groupTwo));
//Uses Members role info
// Uses Members role info
assertEquals(SiteModel.SITE_MANAGER, siteService.getMembersRoleInfo(testGroupMembership, USER_ONE).getMemberRole());
/**
* Check we can filter this list by name and role correctly
@@ -2111,7 +2066,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertNotNull(members);
assertEquals(0, members.size());
// - filter by name - group name
members = siteService.listMembers(testGroupMembership, GROUP_TWO, null, 0, false);
assertNotNull(members);
@@ -2132,7 +2086,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertNotNull(members);
assertEquals(0, members.size());
/**
* Add a group member (USER_THREE) as an explicit member
*/
@@ -2180,12 +2133,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
/**
* Add other group (GROUP_3) with higher (MANAGER) role
*
* - is group in list?
* - is new user a member?
* - does redefined user have highest role?
* USER_TWO should be Manager from group 3 having higher priority than group 2
* USER_THREE should still be Contributor from explicit membership.
* USER_FOUR should be Manager - from group 4 sub-group
* - is group in list? - is new user a member? - does redefined user have highest role? USER_TWO should be Manager from group 3 having higher priority than group 2 USER_THREE should still be Contributor from explicit membership. USER_FOUR should be Manager - from group 4 sub-group
*/
siteService.setMembership(testGroupMembership, groupThree, SiteModel.SITE_MANAGER);
@@ -2229,7 +2177,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
*
* See https://issues.alfresco.com/jira/browse/MNT-2229
*/
@Test
@@ -2262,8 +2209,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
@Test
public void testSiteVisibility()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -2352,7 +2298,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
*/
private Map<String, Set<String>> getAllowedPermissionsMap(NodeRef nodeRef)
{
Map<String,Set<String>> perms = new HashMap<String, Set<String>>();
Map<String, Set<String>> perms = new HashMap<String, Set<String>>();
for (AccessPermission ap : permissionService.getAllSetPermissions(nodeRef))
{
if (ap.getAccessStatus() == AccessStatus.ALLOWED)
@@ -2370,8 +2316,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* ALF-10343 - When the default public group for sites isn't EVERYBODY,
* check that creating and altering sites results in the correct permissions
* ALF-10343 - When the default public group for sites isn't EVERYBODY, check that creating and altering sites results in the correct permissions
*/
@Test
public void testNonDefaultPublicGroupPermissions() throws Exception
@@ -2384,13 +2329,11 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
sp.setSitePublicGroup(groupFour);
siteServiceImpl.setSysAdminParams(sp);
// Create sites of the three types
SiteInfo s1 = this.siteService.createSite(TEST_SITE_PRESET, "SiteTest_priv", "priv", "priv", SiteVisibility.PRIVATE);
SiteInfo s2 = this.siteService.createSite(TEST_SITE_PRESET, "SiteTest_mod", "mod", "mod", SiteVisibility.MODERATED);
SiteInfo s3 = this.siteService.createSite(TEST_SITE_PRESET, "SiteTest_pub", "pub", "pub", SiteVisibility.PUBLIC);
// Check the permissions on them
// Everyone has read permissions only, not Consumer
assertTrue(getAllowedPermissionsMap(s1).get(PermissionService.ALL_AUTHORITIES).contains("ReadPermissions"));
@@ -2407,7 +2350,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertEquals(SiteModel.SITE_MANAGER, siteService.getMembersRole(s2.getShortName(), USER_ONE));
assertEquals(SiteModel.SITE_MANAGER, siteService.getMembersRole(s3.getShortName(), USER_ONE));
// Swap the visibilites around, private+moderated -> public, public -> private
s1.setVisibility(SiteVisibility.PUBLIC);
s2.setVisibility(SiteVisibility.PUBLIC);
@@ -2433,7 +2375,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertEquals(SiteModel.SITE_MANAGER, siteService.getMembersRole(s2.getShortName(), USER_ONE));
assertEquals(SiteModel.SITE_MANAGER, siteService.getMembersRole(s3.getShortName(), USER_ONE));
// Swap them back again
s1.setVisibility(SiteVisibility.PRIVATE);
s2.setVisibility(SiteVisibility.MODERATED);
@@ -2466,8 +2407,8 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* Creates a site with a simple content tree within it.
* The content looks like
* Creates a site with a simple content tree within it. The content looks like
*
* <pre>
* [site] {siteShortName}
* |
@@ -2484,10 +2425,14 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
* |-- [cm:content] fileFolderPrefix + "fileInSubfolder.txt"
* </pre>
*
* @param siteShortName short name for the site
* @param componentId the component id for the container
* @param visibility visibility for the site.
* @param fileFolderPrefix a prefix String to put on all folders/files created.
* @param siteShortName
* short name for the site
* @param componentId
* the component id for the container
* @param visibility
* visibility for the site.
* @param fileFolderPrefix
* a prefix String to put on all folders/files created.
*/
private SiteInfo createTestSiteWithContent(String siteShortName, String componentId, SiteVisibility visibility, String fileFolderPrefix)
{
@@ -2565,17 +2510,13 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* Create a site with a USER manager.
* Add Group manager membership.
* Create a site with a USER manager. Add Group manager membership.
*
* Lower User membership - should be O.K. because of Group Membership
* Lower Group membership - should be prevented (last manager)
* Lower User membership - should be O.K. because of Group Membership Lower Group membership - should be prevented (last manager)
*
* Reset User membership to Manager
*
* Lower Group membership - should be O.K. because of User Membership
* Lower User membership - should be prevented (last manager)
*
* Lower Group membership - should be O.K. because of User Membership Lower User membership - should be prevented (last manager)
*/
@Test
public void testALFCOM_3109()
@@ -2631,17 +2572,13 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* Create a site with a USER manager.
* Add Group manager membership.
* Create a site with a USER manager. Add Group manager membership.
*
* Remove User membership - should be O.K. because of Group Membership
* Remove Group membership - should be prevented (last manager)
* Remove User membership - should be O.K. because of Group Membership Remove Group membership - should be prevented (last manager)
*
* Add User membership to Manager
*
* Remove Group membership - should be O.K. because of User Membership
* Remove User membership - should be prevented (last manager)
*
* Remove Group membership - should be O.K. because of User Membership Remove User membership - should be prevented (last manager)
*/
@Test
public void testALFCOM_3111()
@@ -2700,13 +2637,11 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
* Create a private site.
*
* Attempt to access a private site by someone that is not a consumer of that site.
*
*/
@Test
public void testETHREEOH_1268()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>() {
@Override
public Object execute() throws Throwable
@@ -2739,11 +2674,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* ALF-3200
* You shouldn't be able to rename a Site using the normal node service
* type operations, because the relationship between a site and its
* authorities is based on a pattern that uses the site name.
* However, you are free to change a site's display name.
* ALF-3200 You shouldn't be able to rename a Site using the normal node service type operations, because the relationship between a site and its authorities is based on a pattern that uses the site name. However, you are free to change a site's display name.
*/
@Test
public void testALF_3200() throws Exception
@@ -2762,7 +2693,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
fileFolderService.rename(siteNodeRef, "RenamedName");
fail("Shouldn't be able to rename a site but did");
}
catch(SiteServiceException e)
catch (SiteServiceException e)
{
// expected
}
@@ -2868,8 +2799,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
/**
* ALF-1017 - Non sites in the Sites Space container shouldn't
* break the listing methods
* ALF-1017 - Non sites in the Sites Space container shouldn't break the listing methods
*/
@Test
public void testALF_1017_nonSitesInSitesSpace() throws Exception
@@ -2886,39 +2816,39 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
// Listing is still ok
sites = this.siteService.listSites(null, null);
assertNotNull("sites list was null.", sites);
assertEquals(preexistingSitesCount+2, sites.size());
assertEquals(preexistingSitesCount + 2, sites.size());
// Now add a random folder, and a random document to the sites root
final NodeRef sitesSpace = this.nodeService.getPrimaryParent(site1.getNodeRef()).getParentRef();
final NodeRef folder = AuthenticationUtil.runAsSystem(new RunAsWork<NodeRef>() {
@Override
public NodeRef doWork() throws Exception {
public NodeRef doWork() throws Exception
{
return nodeService.createNode(
sitesSpace, ContentModel.ASSOC_CONTAINS,
QName.createQName("Folder"), ContentModel.TYPE_FOLDER
).getChildRef();
QName.createQName("Folder"), ContentModel.TYPE_FOLDER).getChildRef();
}
});
final NodeRef document = AuthenticationUtil.runAsSystem(new RunAsWork<NodeRef>() {
@Override
public NodeRef doWork() throws Exception {
public NodeRef doWork() throws Exception
{
return nodeService.createNode(
sitesSpace, ContentModel.ASSOC_CONTAINS,
QName.createQName("Document"), ContentModel.TYPE_CONTENT
).getChildRef();
QName.createQName("Document"), ContentModel.TYPE_CONTENT).getChildRef();
}
});
// Listing should still be fine, and count won't have increased
sites = this.siteService.listSites(null, null);
assertNotNull("sites list was null.", sites);
assertEquals(preexistingSitesCount+2, sites.size());
assertEquals(preexistingSitesCount + 2, sites.size());
// Delete one site, listing still ok
this.siteService.deleteSite(site2.getShortName());
sites = this.siteService.listSites(null, null);
assertNotNull("sites list was null.", sites);
assertEquals(preexistingSitesCount+1, sites.size());
assertEquals(preexistingSitesCount + 1, sites.size());
// Tidy up the random nodes, listing still fine
this.nodeService.deleteNode(folder);
@@ -2926,7 +2856,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
sites = this.siteService.listSites(null, null);
assertNotNull("sites list was null.", sites);
assertEquals(preexistingSitesCount+1, sites.size());
assertEquals(preexistingSitesCount + 1, sites.size());
}
private SiteInfo createSite(String siteShortName, String componentId, SiteVisibility visibility)
@@ -2960,7 +2890,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
fail("Should have caught rename");
}
catch(SiteServiceException e)
catch (SiteServiceException e)
{
assertTrue(e.getMessage().contains("can not be renamed"));
}
@@ -3088,7 +3018,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
return null;
}
/**
* From CLOUD-957, insure that GROUP_EVERYONE does not have read access to private sites' containers.
*/
@@ -3103,7 +3032,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertNull("GROUP_EVERYONE shouldn't have any permissions on a private site's containers", getAllowedPermissionsMap(container).get(PermissionService.ALL_AUTHORITIES));
}
/**
@@ -3120,7 +3048,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertNull("GROUP_EVERYONE shouldn't have any permissions on a moderated site's containers", getAllowedPermissionsMap(container).get(PermissionService.ALL_AUTHORITIES));
}
/**
@@ -3131,7 +3058,7 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
{
String siteName = GUID.generate();
//Check Private->public
// Check Private->public
SiteInfo siteInfo = createSite(siteName, "doclib", SiteVisibility.PRIVATE);
NodeRef container = this.siteService.getContainer(siteInfo.getShortName(), "doclib");
@@ -3141,19 +3068,19 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
assertTrue(getAllowedPermissionsMap(container).get(PermissionService.ALL_AUTHORITIES).contains("ReadPermissions"));
//Check public->moderated
// Check public->moderated
siteInfo.setVisibility(SiteVisibility.MODERATED);
siteService.updateSite(siteInfo);
assertNull("GROUP_EVERYONE shouldn't have any permissions on a moderated site's containers", getAllowedPermissionsMap(container).get(PermissionService.ALL_AUTHORITIES));
//Check moderated->public
// Check moderated->public
siteInfo.setVisibility(SiteVisibility.PUBLIC);
siteService.updateSite(siteInfo);
assertTrue(getAllowedPermissionsMap(container).get(PermissionService.ALL_AUTHORITIES).contains("ReadPermissions"));
//Check public->private
// Check public->private
siteInfo.setVisibility(SiteVisibility.PRIVATE);
siteService.updateSite(siteInfo);
@@ -3162,7 +3089,8 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
}
@Test
public void testDeleteSiteAsAdministrator() throws Exception {
public void testDeleteSiteAsAdministrator() throws Exception
{
this.authenticationComponent.setCurrentUser(AuthenticationUtil.getAdminUserName());
String shortName = GUID.generate();
createSite(shortName, "doclib", SiteVisibility.MODERATED);
@@ -3185,4 +3113,34 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest
siteService.deleteSite(shortName);
}
@Test
public void testFindSitesQueryWithReservedCharacter()
{
// given
SiteServiceImpl cut = new SiteServiceImpl();
ArgumentCaptor<SearchParameters> searchParametersCaptor = ArgumentCaptor.forClass(SearchParameters.class);
SimpleCache<String, Object> cache = new MemoryCache<>();
cache.put("key.sitehome.noderef", new NodeRef("mock", "mock", "mock"));
cut.setSingletonCache(cache);
SearchService searchService = Mockito.mock(SearchService.class);
cut.setSearchService(searchService);
when(searchService.query(any(SearchParameters.class))).thenReturn(new EmptyResultSet());
// when
cut.findSites("-chu", 5);
// then
verify(searchService).query(searchParametersCaptor.capture());
SearchParameters actualSearchParameters = searchParametersCaptor.getValue();
assertThat(actualSearchParameters.getQuery())
.isEqualTo("+TYPE:\"{http://www.alfresco.org/model/site/1.0}site\""
+ " AND ( cm:name:\"\\-chu*\""
+ " OR cm:title: (\"\\-chu*\" )"
+ " OR cm:description:\"\\-chu\")");
}
}

View File

@@ -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
@@ -32,20 +32,19 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextCustomizerFactories;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Base test class providing Hibernate sessions.
* <p>
* By default this is auto-wired by type. If a this is going to
* result in a conlict the use auto-wire by name. This can be done by
* setting populateProtectedVariables to true in the constructor and
* then adding protected members with the same name as the bean you require.
* By default this is auto-wired by type. If a this is going to result in a conlict the use auto-wire by name. This can be done by setting populateProtectedVariables to true in the constructor and then adding protected members with the same name as the bean you require.
*
* @author Derek Hulley
*/
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:alfresco/application-context.xml"})
@ContextCustomizerFactories(factories = {}, mergeMode = ContextCustomizerFactories.MergeMode.REPLACE_DEFAULTS)
public abstract class BaseSpringTest extends TestCase
{
public Log logger = LogFactory.getLog(getClass().getName());