Compare commits

...

118 Commits

Author SHA1 Message Date
alfresco-build
07cc9aa86e [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-09 00:06:55 +00:00
alfresco-build
7db550cee1 [maven-release-plugin][skip ci] prepare release 20.139 2023-04-09 00:06:52 +00:00
Alfresco CI User
937d80327f [force] Force release for 2023-04-09. 2023-04-09 00:03:20 +00:00
alfresco-build
8abff868cc [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-07 13:02:26 +00:00
alfresco-build
ffae8a62a1 [maven-release-plugin][skip ci] prepare release 20.138 2023-04-07 13:02:23 +00:00
kmagdziarz
0350c966df [ACS-5020] Ignore intermittent tests 2023-04-07 14:14:33 +02:00
kmagdziarz
833a7675bc [ACS-5020] Ignore intermittent test 2023-04-07 11:28:35 +02:00
Kacper Magdziarz
8aa263f377 [ACS-4462] Add connection manager builder for httpClient4Factory (#1864)
Co-authored-by: Marcin Strankowski <74721865+mstrankowski@users.noreply.github.com>
2023-04-06 16:06:03 +02:00
alfresco-build
73f30b7ef2 [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-05 15:55:43 +00:00
alfresco-build
4bf58cc99c [maven-release-plugin][skip ci] prepare release 20.137 2023-04-05 15:55:40 +00:00
MohinishSah
d53881d87f Merge pull request #1862 from Alfresco/fix/MNT-22888
MNT-22888: Fix for WikiPages with Link
2023-04-05 15:10:19 +00:00
rrajoria
937601020d MNT-22888: Fix for WikiPages with Link 2023-04-05 18:51:34 +05:30
alfresco-build
429b389b08 [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-05 12:28:56 +00:00
alfresco-build
c944f3560c [maven-release-plugin][skip ci] prepare release 20.136 2023-04-05 12:28:53 +00:00
Sara
40ff2558f5 Feature/acs 4718 remove rmi email service (#1843)
* ACS-4718 Remove EmailServiceRemotable.java

* ACS-4718 Remove use of Rmi in SubethaEmailMessage classes

* ACS-4718 Remove Rmi classes called by SubethaEmailMessage classes

* ACS-4718 Remove rmi related classes

* ACS-4718 Cleanup email classes after rmi removal

---------

Co-authored-by: pzurek <Piotr.Zurek@hyland.com>
2023-04-05 12:47:30 +01:00
Tom Page
4f69f28586 ACS-4932 Fix issues around renaming tags. (#1847)
* ACS-4932 Rename tag node without the id changing.

* ACS-4932 Use fake tag to fire events for all nodes.

* ACS-4932 Use event generator to create events for renamed tags.

* ACS-4932 Fix unit test.

* ACS-4932 Fix tag update tests.

* ACS-4932 Tag names are always lower case.

* ACS-4932 Update TAS tag tests to use lowercase tag names.
2023-04-05 11:37:16 +01:00
alfresco-build
aa66dd748f [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-05 07:07:10 +00:00
alfresco-build
bf9c8ab870 [maven-release-plugin][skip ci] prepare release 20.135 2023-04-05 07:07:07 +00:00
Kacper Magdziarz
959cf8f13f Feature/acs 4462 mtls e2e test (#1861)
ACS-4462 add empty response handling
2023-04-05 08:22:25 +02:00
alfresco-build
8b4285afbd [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-05 05:19:09 +00:00
alfresco-build
7143f5975f [maven-release-plugin][skip ci] prepare release 20.134 2023-04-05 05:19:05 +00:00
Piotr Żurek
31944bfc0f ACS-4971 Restore IDS public client configuration (#1860) 2023-04-05 06:30:26 +02:00
alfresco-build
c874fa4e51 [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-04 13:19:36 +00:00
alfresco-build
5780871f0c [maven-release-plugin][skip ci] prepare release 20.133 2023-04-04 13:19:33 +00:00
kcichonczyk
85ef347dc3 [ACS-4839] bump ATS versions (#1859) 2023-04-04 14:34:18 +02:00
Domenico Sibilio
0a76858778 ACS-4970 Bump json-path to 2.8.0 and json-smart to 2.4.10 (#1854) 2023-04-04 14:03:18 +02:00
alfresco-build
6db6245e53 [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-04 08:13:27 +00:00
alfresco-build
7b572a3e02 [maven-release-plugin][skip ci] prepare release 20.132 2023-04-04 08:13:24 +00:00
Piotr Żurek
b1cbebacc3 ACS-4891 Configurable Public Key Cache (#1858) 2023-04-04 09:25:22 +02:00
alfresco-build
0bbe73aee1 [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-03 19:15:09 +00:00
alfresco-build
9135fdae8b [maven-release-plugin][skip ci] prepare release 20.131 2023-04-03 19:15:05 +00:00
MohinishSah
0cb0cfa8f6 Merge pull request #1857 from Alfresco/ACA-4689
added the new email template
2023-04-03 16:46:48 +00:00
alfresco-build
5a96cce5bb [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-03 16:20:26 +00:00
alfresco-build
c999fb25b9 [maven-release-plugin][skip ci] prepare release 20.130 2023-04-03 16:20:23 +00:00
Mohinish Sah
3c2643dbf2 added the new email template 2023-04-03 21:09:46 +05:30
Piotr Żurek
106e398393 ACS-4895 Restore support for providing IDS public key (#1856) 2023-04-03 17:35:14 +02:00
alfresco-build
1c45748f9a [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-03 09:15:11 +00:00
alfresco-build
2b7ff213a9 [maven-release-plugin][skip ci] prepare release 20.129 2023-04-03 09:15:08 +00:00
Piotr Żurek
f693c49e87 ACS-4893 ACS-4890 Restore connection/TLS/mTLS IDS configuration (#1853)
* Unify http client for all IDS related activities
* Restore connection configuration
* Restore TLS/mTLS configuration
2023-04-03 10:30:01 +02:00
alfresco-build
0f156ba259 [maven-release-plugin][skip ci] prepare for next development iteration 2023-04-02 00:06:41 +00:00
alfresco-build
31a0a51290 [maven-release-plugin][skip ci] prepare release 20.128 2023-04-02 00:06:38 +00:00
Alfresco CI User
c8fe044993 [force] Force release for 2023-04-02. 2023-04-02 00:03:22 +00:00
alfresco-build
28058864e6 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-31 14:13:44 +00:00
alfresco-build
23ccb397f4 [maven-release-plugin][skip ci] prepare release 20.127 2023-03-31 14:13:40 +00:00
Piotr Żurek
73ae0d0a6e ACS-4847 Remove Keycloak dependencies (#1848) 2023-03-31 15:28:14 +02:00
alfresco-build
f9b5e5a6fe [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-31 10:27:19 +00:00
alfresco-build
e5bfb0298d [maven-release-plugin][skip ci] prepare release 20.126 2023-03-31 10:27:16 +00:00
Piotr Żurek
34cb9bf76a ACS-4847 Bump AOS module (#1846) 2023-03-31 10:38:09 +02:00
alfresco-build
fcbd406170 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-30 12:26:47 +00:00
alfresco-build
730ab768e9 [maven-release-plugin][skip ci] prepare release 20.125 2023-03-30 12:26:43 +00:00
Kacper Magdziarz
fab591eb9b [ACS-4459] Investigate and extend/universalize current custom Solr mTLS implementation in Repository (#1735)
* ACS-4459 Add new HttpClient Factory for Mutual TLS and implement it for Transform Service
* ACS-4462 Add e2e for MTLS
2023-03-30 13:43:42 +02:00
alfresco-build
5bb96729fc [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-29 13:04:41 +00:00
alfresco-build
a80f2c4db7 [maven-release-plugin][skip ci] prepare release 20.124 2023-03-29 13:04:38 +00:00
Piotr Żurek
82df7ce5d4 ACS-4847 Expose authorization code and refresh token grant types for the AOS (#1836) 2023-03-29 14:16:08 +02:00
alfresco-build
73a1de37f6 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-29 11:22:30 +00:00
alfresco-build
63f9a8e142 [maven-release-plugin][skip ci] prepare release 20.123 2023-03-29 11:22:25 +00:00
rrajoria
f668ed9198 Updating webscript version 2023-03-29 15:33:18 +05:30
alfresco-build
ea15673116 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-29 08:46:12 +00:00
alfresco-build
6a2a298445 [maven-release-plugin][skip ci] prepare release 20.122 2023-03-29 08:46:08 +00:00
pksingh41
4b0a6d27f9 Merge pull request #1833 from Alfresco/ACS-4636-CreateTagCountParamSupport
[ACS-4636] create tag count param support
2023-03-29 13:26:45 +05:30
Praveen Singh
b432fdc6a3 Addressed review comments 2023-03-29 11:17:00 +05:30
alfresco-build
7d62c89708 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-28 11:09:25 +00:00
alfresco-build
e79fcf64a5 [maven-release-plugin][skip ci] prepare release 20.121 2023-03-28 11:09:22 +00:00
kcichonczyk
5df4cfff58 [ACS-4929][ACS-4927] transform-service and transform-core version bump (#1837) 2023-03-28 11:42:33 +02:00
alfresco-build
66ab0c4f46 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-28 07:41:54 +00:00
alfresco-build
1d1252438c [maven-release-plugin][skip ci] prepare release 20.120 2023-03-28 07:41:50 +00:00
MohinishSah
e4ac036f9f Merge pull request #1609 from Alfresco/feature/ACA-4619-Self-service-User-password-reset-ADW
Feature/aca 4619 self service user password reset adw
2023-03-28 06:56:49 +00:00
suneet.gupta
a9ec842358 Merge branch 'master' of github.com:Alfresco/alfresco-community-repo into ACS-4636-CreateTagCountParamSupport 2023-03-28 12:18:42 +05:30
alfresco-build
9590051941 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-27 17:12:19 +00:00
alfresco-build
37618bd56a [maven-release-plugin][skip ci] prepare release 20.119 2023-03-27 17:12:14 +00:00
Tom Page
8bb30643d4 Merge pull request #1790 from Alfresco/feature/ACS-4779-Count_isssue_for_GET_call
ACS-4779:Count field issue for GET call
2023-03-27 17:23:27 +01:00
Tom Page
f0e0819bc5 ACS-4779 Remove failing integration test.
There is no way that the changes to TagsImpl should have affected shared links and so this
test must be failing due to interactions with other tests (e.g. a change in ordering, or
unexpected data in the system).

Notably a comment above the test suggests it may be duplicated by testSharedLinkCreateGetDelete
(which is another unweildy test that covers a large number of difference scenarios). The two
tests definitely contain a large amount of duplicated code.
2023-03-27 16:32:13 +01:00
suneet.gupta
5999a448d4 Merge branch 'master' of github.com:Alfresco/alfresco-community-repo into ACS-4636-CreateTagCountParamSupport 2023-03-27 13:57:30 +05:30
Mohinish Sah
8f00b44a03 Merge remote-tracking branch 'origin/master' into feature/ACA-4619-Self-service-User-password-reset-ADW 2023-03-27 13:07:01 +05:30
alfresco-build
c0d1b33201 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-26 00:06:25 +00:00
alfresco-build
4053daaf1a [maven-release-plugin][skip ci] prepare release 20.118 2023-03-26 00:06:23 +00:00
Alfresco CI User
6942294780 [force] Force release for 2023-03-26. 2023-03-26 00:03:13 +00:00
Tom Page
384177a9d9 Merge pull request #1826 from Alfresco/feature/ACS-4863_ValidateNodeWithoutPath
ACS-4863 Add method validateOrLookupNode without path.
2023-03-24 13:53:44 +00:00
alfresco-build
bbdeb5ae97 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-24 12:17:17 +00:00
alfresco-build
95fb286c14 [maven-release-plugin][skip ci] prepare release 20.117 2023-03-24 12:17:14 +00:00
Tom Page
0652cea296 ACS-4863 Move new validation method to interface as a default implementation. 2023-03-24 11:44:55 +00:00
Tom Page
a86a954771 Merge pull request #1829 from Alfresco/feature/ACS-4863_InvestigateTestFailure
ACS-4863 Restore code to allow aliases in tag and category APIs.
2023-03-24 11:37:15 +00:00
Tom Page
c27a44d711 ACS-4863 Remove test for invalid URL.
This test behaves differently against community and enterprise as the double slash is treated differently.
2023-03-24 10:16:28 +00:00
Krystian Dabrowski
ef089472fb ACS-4915: Editing an orphaned tag results in the tag being deleted (#1823)
* ACS-4915: Editing an orphaned tag results in the tag being deleted
2023-03-24 10:55:12 +01:00
alfresco-build
5769cbe54e [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-24 09:03:52 +00:00
alfresco-build
9899f16d61 [maven-release-plugin][skip ci] prepare release 20.116 2023-03-24 09:03:48 +00:00
Tom Page
219acd6c53 Revert "Revert "ACS-4863 Allow referencing nodes by aliases for tag and category application. (#1822)""
This reverts commit 70b7d5a1f8.
2023-03-24 08:21:11 +00:00
Tom Page
70b7d5a1f8 Revert "ACS-4863 Allow referencing nodes by aliases for tag and category application. (#1822)"
This reverts commit dafa77d0a0.
2023-03-24 08:20:50 +00:00
alfresco-build
279679d78a [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-24 07:03:42 +00:00
alfresco-build
6d1dcf6c7e [maven-release-plugin][skip ci] prepare release 20.115 2023-03-24 07:03:39 +00:00
suneet.gupta
18810de7d0 Merge branch 'master' of github.com:Alfresco/alfresco-community-repo into ACS-4636-CreateTagCountParamSupport 2023-03-24 08:15:46 +05:30
Piotr Żurek
fb119565dc ACS-4888 Use Keycloak free tas-utility (#1827) 2023-03-23 18:29:19 +01:00
Tom Page
10b0d7b5f0 ACS-4863 Ensure we don't accidentally introduce circular reference. 2023-03-23 16:01:07 +00:00
Tom Page
36292d749e ACS-4779 Simplify list creation in test. 2023-03-23 13:58:45 +00:00
Tom Page
c1d424b386 Merge master into ACS-4779-Count_isssue_for_GET_call. 2023-03-23 13:56:54 +00:00
Tom Page
06c6efc6c9 ACS-4779 Remove duplicate @BeforeClass method. 2023-03-23 13:51:47 +00:00
suneet.gupta
626e5b34ef [ACS-4636] Added count support for creation of tags to a node 2023-03-23 18:35:12 +05:30
Tom Page
a549859cbe ACS-4863 Add method validateOrLookupNode without path. 2023-03-23 09:11:48 +00:00
alfresco-build
f2d858f911 [maven-release-plugin][skip ci] prepare for next development iteration 2023-03-22 17:39:49 +00:00
Tom Page
fd96c90c08 ACS-4779 Remove test for unsupported use case. 2023-03-22 16:02:55 +00:00
Tom Page
a41fcdff0f ACS-4779 Ensure we only include the count for tags in the GET response when requested. 2023-03-22 15:18:37 +00:00
Tom Page
ab461e34af ACS-4779 Tidy test class. 2023-03-22 14:48:01 +00:00
Tom Page
c92ef4393c Merge master into feature branch. 2023-03-22 14:46:15 +00:00
rrajoria
9ddbda377d Test Case for Client Workspace and changes for Review comments 2023-03-22 12:56:45 +05:30
adam.zakrzewski
267bd98d21 ACS-4779: Adding update for tests after Tome's review 2023-03-08 16:14:21 +01:00
adam.zakrzewski
bcaeedb162 ACS-4779:Count field exists in response for get list of tags even when that field is not included. Other issue is that count field is not present when we do GET call with included count for /tags/{tagId} 2023-03-08 14:39:47 +01:00
adam.zakrzewski
f93bced905 ACS-4779:Count field exists in response for get list of tags even when that field is not included. Other issue is that count field is not present when we do GET call with included count for /tags/{tagId} 2023-03-08 14:27:16 +01:00
Mohinish Sah
91f3edf8e9 incorporated review comments 2023-03-02 22:10:54 +05:30
Mohinish Sah
479724365e incorporated review comments 2023-03-02 22:04:50 +05:30
Mohinish Sah
bb1d5899d9 Merge remote-tracking branch 'origin/master' into feature/ACA-4619-Self-service-User-password-reset-ADW 2023-03-02 21:46:53 +05:30
Mohinish Sah
7ab5e8afd0 Updated the client App name 2023-03-01 18:45:08 +05:30
Mohinish Sah
5107fdfe41 resolved review comments 2023-03-01 12:45:15 +05:30
Mohinish Sah
f59ff23a45 Rebase Branch 2022-12-08 16:26:44 +05:30
rrajoria
5e2f1db714 ACA-4619: Forgot Password Changes 2022-12-08 16:21:29 +05:30
rrajoria
67ee2efc60 ACA-4619: Forgot Password Changes 2022-12-08 16:21:29 +05:30
MohinishSah
1e7dc6ed8d update Gdrive version 2022-11-14 10:54:40 +05:30
rrajoria
e5ea6db30c ACA-4619: Forgot Password Changes 2022-11-09 18:11:16 +05:30
rrajoria
78a613b1de ACA-4619: Forgot Password Changes 2022-11-09 18:10:29 +05:30
117 changed files with 4870 additions and 3683 deletions

View File

@@ -53,24 +53,6 @@ updates:
- dependency-name: org.freemarker:freemarker
versions:
- "> 2.3.20-alfresco-patched-20200421"
- dependency-name: org.keycloak:keycloak-adapter-core
versions:
- "> 12.0.2"
- dependency-name: org.keycloak:keycloak-adapter-spi
versions:
- "> 12.0.2"
- dependency-name: org.keycloak:keycloak-authz-client
versions:
- "> 12.0.2"
- dependency-name: org.keycloak:keycloak-common
versions:
- "> 12.0.2"
- dependency-name: org.keycloak:keycloak-core
versions:
- "> 12.0.2"
- dependency-name: org.keycloak:keycloak-servlet-adapter-spi
versions:
- "> 12.0.2"
- dependency-name: org.eclipse.jetty:jetty-server
versions:
- 9.4.38.v20210224

View File

@@ -313,6 +313,10 @@ jobs:
- testSuite: SearchTestSuite
compose-profile: default
mvn-options: '-Dindex.subsystem.name=solr6'
- testSuite: MTLSTestSuite
compose-profile: with-mtls-transform-core-aio
mtls: true
mvn-options: '-Dencryption.ssl.keystore.location=${GITHUB_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${GITHUB_WORKSPACE}/keystores/alfresco/alfresco.truststore'
steps:
- uses: actions/checkout@v3
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
@@ -321,6 +325,11 @@ jobs:
run: bash ./scripts/ci/init.sh
- name: "Set transformers tag"
run: echo "TRANSFORMERS_TAG=$(mvn help:evaluate -Dexpression=dependency.alfresco-transform-core.version -q -DforceStdout)" >> $GITHUB_ENV
- name: "Generate Keystores and Truststores for Mutual TLS configuration"
if: ${{ matrix.mtls }}
run: |
git clone -b "master" --depth=1 "https://${{ secrets.BOT_GITHUB_USERNAME }}:${{ secrets.BOT_GITHUB_TOKEN }}@github.com/Alfresco/alfresco-ssl-generator.git"
bash ./scripts/ci/generate_keystores.sh
- name: "Set up the environment"
run: |
if [ -e ./scripts/ci/tests/${{ matrix.testSuite }}-setup.sh ]; then

View File

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

View File

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

View File

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

View File

@@ -50,6 +50,7 @@ import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
import org.junit.Ignore;
import org.springframework.beans.factory.annotation.Autowired;
import org.testng.AssertJUnit;
import org.testng.annotations.BeforeClass;
@@ -135,192 +136,195 @@ public class DispositionScheduleLinkedRecordsTest extends BaseRMRestTest {
* <p>
* <p/> TestRail Test C775<p/>
**/
@Test
@AlfrescoTest(jira = "RM-1622")
public void dispositionScheduleLinkedRecords() throws UnsupportedEncodingException {
STEP("Create record category");
Category1 = createRootCategory(categoryRM3077);
// @Ignore("ACS-5020")
// @Test
// @AlfrescoTest(jira = "RM-1622")
// public void dispositionScheduleLinkedRecords() throws UnsupportedEncodingException {
// STEP("Create record category");
// Category1 = createRootCategory(categoryRM3077);
//
// //create retention schedule
// dispositionScheduleService.createCategoryRetentionSchedule(Category1.getName(), false);
//
// // add cut off step
// dispositionScheduleService.addCutOffAfterPeriodStep(Category1.getName(), "day|2", CREATED_DATE);
//
// //create a copy of the category recordsCategory
// String CopyCategoryId = copyCategory(getAdminUser(),Category1.getId(), copyCategoryRM3077);
//
// // create folders in both categories
// CatFolder = createRecordFolder(Category1.getId(), folderRM3077);
// CopyCatFolder = createRecordFolder(CopyCategoryId, copyFolderRM3077);
//
// // create record files
// String electronicRecord = "RM-2801 electronic record";
// Record elRecord = createElectronicRecord(CatFolder.getId(), electronicRecord);
// String elRecordFullName = recordsAPI.getRecordFullName(getDataUser().usingAdmin().getAdminUser().getUsername(),
// getDataUser().usingAdmin().getAdminUser().getPassword(), CatFolder.getName(), electronicRecord);
//
// String nonElectronicRecord = "RM-2801 non-electronic record";
// Record nonElRecord = createNonElectronicRecord(CatFolder.getId(), nonElectronicRecord);
// String nonElRecordFullName = recordsAPI.getRecordFullName(getDataUser().usingAdmin().getAdminUser().getUsername(),
// getDataUser().usingAdmin().getAdminUser().getPassword(), CatFolder.getName(), nonElectronicRecord);
//
// // link the records to copy folder, then complete them
// List<String> recordLists = new ArrayList<>();
// recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + elRecord.getId());
// recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + nonElRecord.getId());
//
// linksAPI.linkRecord(getDataUser().getAdminUser().getUsername(),
// getDataUser().getAdminUser().getPassword(), HttpStatus.SC_OK,copyCategoryRM3077 + "/" +
// copyFolderRM3077, recordLists);
// recordsAPI.completeRecord(rmAdmin.getUsername(), rmAdmin.getPassword(), elRecordFullName);
// recordsAPI.completeRecord(rmAdmin.getUsername(), rmAdmin.getPassword(), nonElRecordFullName);
//
// // edit disposition date
// recordFoldersAPI.postFolderAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),editDispositionDateJson(),CatFolder.getName());
//
// // cut off the Folder
// recordFoldersAPI.postFolderAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","cutoff"),CatFolder.getName());
//
// // Verify the Content
// Node electronicNode = getNode(elRecord.getId());
// assertTrue("The content of " + electronicRecord + " is available",
// StringUtils.isEmpty(electronicNode.getNodeContent().getResponse().getBody().asString()));
//
// // verify the Properties
// AssertJUnit.assertNull("The properties are present even after cutting off the record.", elRecord.getProperties().getTitle());
//
// // delete precondition
// deleteRecordCategory(Category1.getId());
// deleteRecordCategory(CopyCategoryId);
// }
// /**
// * Test covering RM-3060
// * Check the disposition steps for a record can be executed
// * When the record is linked to a folder with the same disposition schedule
// * */
// @Ignore("ACS-5020")
//// @Test
// @AlfrescoTest (jira = "RM-3060")
// public void sameDispositionScheduleLinkedRecords() throws UnsupportedEncodingException {
//
// // create a category with retention applied on records level
// RecordCategory recordCategory = getRestAPIFactory().getFilePlansAPI(rmAdmin)
// .createRootRecordCategory(RecordCategory.builder().name(firstCategoryRM3060).build(),
// RecordCategory.DEFAULT_FILE_PLAN_ALIAS);
// dispositionScheduleService.createCategoryRetentionSchedule(firstCategoryRM3060, true);
// dispositionScheduleService.addCutOffAfterPeriodStep(firstCategoryRM3060, "week|1", DATE_FILED);
// dispositionScheduleService.addTransferAfterEventStep(firstCategoryRM3060, TRANSFER_LOCATION, RMEvents.CASE_CLOSED.getEventName());
// dispositionScheduleService.addDestroyWithoutGhostingAfterPeriodStep(firstCategoryRM3060, "week|1", CUT_OFF_DATE);
//
// // make a copy of the category created
// String categorySecondId = copyCategory(getAdminUser(), recordCategory.getId(), secondCategoryRM3060);
//
// // create a folder on the category firstCategoryRM3060 with a complete electronic record
// RecordCategoryChild firstFolderRecordCategoryChild = createRecordFolder(recordCategory.getId(),firstFolderRM3060);
// Record firstElectronicRecord = createElectronicRecord(firstFolderRecordCategoryChild.getId(),electronicRecordRM3060);
//
// String elRecordFullName = recordsAPI.getRecordFullName(getDataUser().getAdminUser().getUsername(),
// getDataUser().getAdminUser().getPassword(),firstFolderRM3060, electronicRecordRM3060);
// String elRecordNameNodeRef = recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
// getDataUser().usingAdmin().getAdminUser().getPassword(), elRecordFullName, "/" + firstCategoryRM3060 + "/" + firstFolderRM3060);
//
// recordsAPI.completeRecord(getDataUser().getAdminUser().getUsername(),
// getDataUser().getAdminUser().getPassword(), elRecordFullName);
//
// // create a folder on the category secondCategoryRM3060 with a non electronic record
// RecordCategoryChild secondFolderRecordCategoryChild = createRecordFolder(categorySecondId,secondFolderRM3060);
// Record secondNonElectronicRecord = createNonElectronicRecord(secondFolderRecordCategoryChild.getId(),nonElectronicRecordRM3060);
//
// // link the nonElectronicRecordRM3060 to firstFolderRM3060
// List<String> recordLists = new ArrayList<>();
// recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + secondNonElectronicRecord.getId());
//
// linksAPI.linkRecord(getDataUser().getAdminUser().getUsername(),
// getDataUser().getAdminUser().getPassword(), HttpStatus.SC_OK,secondCategoryRM3060 + "/" +
// secondFolderRM3060, recordLists);
// String nonElRecordFullName = recordsAPI.getRecordFullName(getDataUser().usingAdmin().getAdminUser().getUsername(),
// getDataUser().usingAdmin().getAdminUser().getPassword(), secondFolderRM3060, secondNonElectronicRecord.getName());
// String nonElRecordNameNodeRef = recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
// getDataUser().usingAdmin().getAdminUser().getPassword(), nonElRecordFullName, "/" + secondCategoryRM3060 + "/" + secondFolderRM3060);
//
// // complete records and cut them off
// recordsAPI.completeRecord(getDataUser().getAdminUser().getUsername(),
// getDataUser().getAdminUser().getPassword(), nonElRecordFullName);
//
// // edit the disposition date
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),editDispositionDateJson(),nonElRecordNameNodeRef);
//
// // cut off the record
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","cutoff"),nonElRecordNameNodeRef);
//
// //check the record is cut off
// AssertJUnit.assertTrue("The file " + nonElectronicRecordRM3060 + " has not been successfully cut off.", getRestAPIFactory().getRecordsAPI().getRecord(secondNonElectronicRecord.getId()).getAspectNames().contains(CUT_OFF_ASPECT));
//
// // link the electronic record to secondFolderRM3060
// recordLists.clear();
// recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + secondNonElectronicRecord.getId());
// linksAPI.linkRecord(getDataUser().getAdminUser().getUsername(),
// getDataUser().getAdminUser().getPassword(), HttpStatus.SC_OK,secondCategoryRM3060 + "/" +
// secondFolderRM3060, recordLists);
//
// // edit the disposition date and cut off the record
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),editDispositionDateJson(),elRecordNameNodeRef);
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","cutoff"),elRecordNameNodeRef);
//
// AssertJUnit.assertTrue("The file " + electronicRecordRM3060 + " has not been successfully cut off.", getRestAPIFactory().getRecordsAPI().getRecord(firstElectronicRecord.getId()).getAspectNames().contains(CUT_OFF_ASPECT));
//
// // open the record and complete the disposition schedule event
// rmRolesAndActionsAPI.completeEvent(getAdminUser().getUsername(),
// getAdminUser().getPassword(), elRecordFullName, RMEvents.CASE_CLOSED, Instant.now());
// rmRolesAndActionsAPI.completeEvent(getAdminUser().getUsername(),
// getAdminUser().getPassword(), nonElRecordFullName, RMEvents.CASE_CLOSED, Instant.now());
//
// // transfer the files & complete transfers
// HttpResponse nonElRecordNameHttpResponse = recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","transfer"),recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
// getDataUser().usingAdmin().getAdminUser().getPassword(), nonElRecordFullName, "/" + secondCategoryRM3060 + "/" + secondFolderRM3060));
//
// String nonElRecordNameTransferId = getTransferId(nonElRecordNameHttpResponse,nonElRecordNameNodeRef);
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","transferComplete"),nonElRecordNameTransferId);
//
// HttpResponse elRecordNameHttpResponse = recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","transfer"),recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
// getDataUser().usingAdmin().getAdminUser().getPassword(), elRecordFullName, "/" + firstCategoryRM3060 + "/" + firstFolderRM3060));
//
// String elRecordNameTransferId = getTransferId(elRecordNameHttpResponse,elRecordNameNodeRef);
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","transferComplete"),elRecordNameTransferId);
//
// AssertJUnit.assertTrue("The file " + electronicRecordRM3060 + " has not been successfully transferred", getRestAPIFactory().getRecordsAPI().getRecord(firstElectronicRecord.getId()).getAspectNames().contains(TRANSFER_TYPE));
// AssertJUnit.assertTrue("The file " + nonElectronicRecordRM3060 + " has not been successfully transferred.", getRestAPIFactory().getRecordsAPI().getRecord(secondNonElectronicRecord.getId()).getAspectNames().contains(TRANSFER_TYPE));
//
// // edit the disposition date for nonElectronicRecordRM3060 & electronicRecordRM3060
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),editDispositionDateJson(),nonElRecordNameNodeRef);
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),editDispositionDateJson(),elRecordNameNodeRef);
//
// // destroy nonElectronicRecordRM3060 & electronicRecordRM3060 records
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","destroy"),nonElRecordNameNodeRef);
// recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
// getAdminUser().getPassword(),new JSONObject().put("name","destroy"),elRecordNameNodeRef);
//
// // check the file is not displayed
// assertNull("The file " + nonElectronicRecordRM3060 + " has not been successfully destroyed.", secondNonElectronicRecord.getContent());
// assertNull("The file " + electronicRecordRM3060 + " has not been successfully destroyed.", firstElectronicRecord.getContent());
//
// // delete precondition
// deleteRecordCategory(recordCategory.getId());
// deleteRecordCategory(categorySecondId);
// }
//create retention schedule
dispositionScheduleService.createCategoryRetentionSchedule(Category1.getName(), false);
// add cut off step
dispositionScheduleService.addCutOffAfterPeriodStep(Category1.getName(), "day|2", CREATED_DATE);
//create a copy of the category recordsCategory
String CopyCategoryId = copyCategory(getAdminUser(),Category1.getId(), copyCategoryRM3077);
// create folders in both categories
CatFolder = createRecordFolder(Category1.getId(), folderRM3077);
CopyCatFolder = createRecordFolder(CopyCategoryId, copyFolderRM3077);
// create record files
String electronicRecord = "RM-2801 electronic record";
Record elRecord = createElectronicRecord(CatFolder.getId(), electronicRecord);
String elRecordFullName = recordsAPI.getRecordFullName(getDataUser().usingAdmin().getAdminUser().getUsername(),
getDataUser().usingAdmin().getAdminUser().getPassword(), CatFolder.getName(), electronicRecord);
String nonElectronicRecord = "RM-2801 non-electronic record";
Record nonElRecord = createNonElectronicRecord(CatFolder.getId(), nonElectronicRecord);
String nonElRecordFullName = recordsAPI.getRecordFullName(getDataUser().usingAdmin().getAdminUser().getUsername(),
getDataUser().usingAdmin().getAdminUser().getPassword(), CatFolder.getName(), nonElectronicRecord);
// link the records to copy folder, then complete them
List<String> recordLists = new ArrayList<>();
recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + elRecord.getId());
recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + nonElRecord.getId());
linksAPI.linkRecord(getDataUser().getAdminUser().getUsername(),
getDataUser().getAdminUser().getPassword(), HttpStatus.SC_OK,copyCategoryRM3077 + "/" +
copyFolderRM3077, recordLists);
recordsAPI.completeRecord(rmAdmin.getUsername(), rmAdmin.getPassword(), elRecordFullName);
recordsAPI.completeRecord(rmAdmin.getUsername(), rmAdmin.getPassword(), nonElRecordFullName);
// edit disposition date
recordFoldersAPI.postFolderAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),editDispositionDateJson(),CatFolder.getName());
// cut off the Folder
recordFoldersAPI.postFolderAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","cutoff"),CatFolder.getName());
// Verify the Content
Node electronicNode = getNode(elRecord.getId());
assertTrue("The content of " + electronicRecord + " is available",
StringUtils.isEmpty(electronicNode.getNodeContent().getResponse().getBody().asString()));
// verify the Properties
AssertJUnit.assertNull("The properties are present even after cutting off the record.", elRecord.getProperties().getTitle());
// delete precondition
deleteRecordCategory(Category1.getId());
deleteRecordCategory(CopyCategoryId);
}
/**
* Test covering RM-3060
* Check the disposition steps for a record can be executed
* When the record is linked to a folder with the same disposition schedule
* */
@Test
@AlfrescoTest (jira = "RM-3060")
public void sameDispositionScheduleLinkedRecords() throws UnsupportedEncodingException {
// create a category with retention applied on records level
RecordCategory recordCategory = getRestAPIFactory().getFilePlansAPI(rmAdmin)
.createRootRecordCategory(RecordCategory.builder().name(firstCategoryRM3060).build(),
RecordCategory.DEFAULT_FILE_PLAN_ALIAS);
dispositionScheduleService.createCategoryRetentionSchedule(firstCategoryRM3060, true);
dispositionScheduleService.addCutOffAfterPeriodStep(firstCategoryRM3060, "week|1", DATE_FILED);
dispositionScheduleService.addTransferAfterEventStep(firstCategoryRM3060, TRANSFER_LOCATION, RMEvents.CASE_CLOSED.getEventName());
dispositionScheduleService.addDestroyWithoutGhostingAfterPeriodStep(firstCategoryRM3060, "week|1", CUT_OFF_DATE);
// make a copy of the category created
String categorySecondId = copyCategory(getAdminUser(), recordCategory.getId(), secondCategoryRM3060);
// create a folder on the category firstCategoryRM3060 with a complete electronic record
RecordCategoryChild firstFolderRecordCategoryChild = createRecordFolder(recordCategory.getId(),firstFolderRM3060);
Record firstElectronicRecord = createElectronicRecord(firstFolderRecordCategoryChild.getId(),electronicRecordRM3060);
String elRecordFullName = recordsAPI.getRecordFullName(getDataUser().getAdminUser().getUsername(),
getDataUser().getAdminUser().getPassword(),firstFolderRM3060, electronicRecordRM3060);
String elRecordNameNodeRef = recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
getDataUser().usingAdmin().getAdminUser().getPassword(), elRecordFullName, "/" + firstCategoryRM3060 + "/" + firstFolderRM3060);
recordsAPI.completeRecord(getDataUser().getAdminUser().getUsername(),
getDataUser().getAdminUser().getPassword(), elRecordFullName);
// create a folder on the category secondCategoryRM3060 with a non electronic record
RecordCategoryChild secondFolderRecordCategoryChild = createRecordFolder(categorySecondId,secondFolderRM3060);
Record secondNonElectronicRecord = createNonElectronicRecord(secondFolderRecordCategoryChild.getId(),nonElectronicRecordRM3060);
// link the nonElectronicRecordRM3060 to firstFolderRM3060
List<String> recordLists = new ArrayList<>();
recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + secondNonElectronicRecord.getId());
linksAPI.linkRecord(getDataUser().getAdminUser().getUsername(),
getDataUser().getAdminUser().getPassword(), HttpStatus.SC_OK,secondCategoryRM3060 + "/" +
secondFolderRM3060, recordLists);
String nonElRecordFullName = recordsAPI.getRecordFullName(getDataUser().usingAdmin().getAdminUser().getUsername(),
getDataUser().usingAdmin().getAdminUser().getPassword(), secondFolderRM3060, secondNonElectronicRecord.getName());
String nonElRecordNameNodeRef = recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
getDataUser().usingAdmin().getAdminUser().getPassword(), nonElRecordFullName, "/" + secondCategoryRM3060 + "/" + secondFolderRM3060);
// complete records and cut them off
recordsAPI.completeRecord(getDataUser().getAdminUser().getUsername(),
getDataUser().getAdminUser().getPassword(), nonElRecordFullName);
// edit the disposition date
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),editDispositionDateJson(),nonElRecordNameNodeRef);
// cut off the record
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","cutoff"),nonElRecordNameNodeRef);
//check the record is cut off
AssertJUnit.assertTrue("The file " + nonElectronicRecordRM3060 + " has not been successfully cut off.", getRestAPIFactory().getRecordsAPI().getRecord(secondNonElectronicRecord.getId()).getAspectNames().contains(CUT_OFF_ASPECT));
// link the electronic record to secondFolderRM3060
recordLists.clear();
recordLists.add(NODE_REF_WORKSPACE_SPACES_STORE + secondNonElectronicRecord.getId());
linksAPI.linkRecord(getDataUser().getAdminUser().getUsername(),
getDataUser().getAdminUser().getPassword(), HttpStatus.SC_OK,secondCategoryRM3060 + "/" +
secondFolderRM3060, recordLists);
// edit the disposition date and cut off the record
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),editDispositionDateJson(),elRecordNameNodeRef);
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","cutoff"),elRecordNameNodeRef);
AssertJUnit.assertTrue("The file " + electronicRecordRM3060 + " has not been successfully cut off.", getRestAPIFactory().getRecordsAPI().getRecord(firstElectronicRecord.getId()).getAspectNames().contains(CUT_OFF_ASPECT));
// open the record and complete the disposition schedule event
rmRolesAndActionsAPI.completeEvent(getAdminUser().getUsername(),
getAdminUser().getPassword(), elRecordFullName, RMEvents.CASE_CLOSED, Instant.now());
rmRolesAndActionsAPI.completeEvent(getAdminUser().getUsername(),
getAdminUser().getPassword(), nonElRecordFullName, RMEvents.CASE_CLOSED, Instant.now());
// transfer the files & complete transfers
HttpResponse nonElRecordNameHttpResponse = recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","transfer"),recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
getDataUser().usingAdmin().getAdminUser().getPassword(), nonElRecordFullName, "/" + secondCategoryRM3060 + "/" + secondFolderRM3060));
String nonElRecordNameTransferId = getTransferId(nonElRecordNameHttpResponse,nonElRecordNameNodeRef);
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","transferComplete"),nonElRecordNameTransferId);
HttpResponse elRecordNameHttpResponse = recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","transfer"),recordsAPI.getRecordNodeRef(getDataUser().usingAdmin().getAdminUser().getUsername(),
getDataUser().usingAdmin().getAdminUser().getPassword(), elRecordFullName, "/" + firstCategoryRM3060 + "/" + firstFolderRM3060));
String elRecordNameTransferId = getTransferId(elRecordNameHttpResponse,elRecordNameNodeRef);
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","transferComplete"),elRecordNameTransferId);
AssertJUnit.assertTrue("The file " + electronicRecordRM3060 + " has not been successfully transferred", getRestAPIFactory().getRecordsAPI().getRecord(firstElectronicRecord.getId()).getAspectNames().contains(TRANSFER_TYPE));
AssertJUnit.assertTrue("The file " + nonElectronicRecordRM3060 + " has not been successfully transferred.", getRestAPIFactory().getRecordsAPI().getRecord(secondNonElectronicRecord.getId()).getAspectNames().contains(TRANSFER_TYPE));
// edit the disposition date for nonElectronicRecordRM3060 & electronicRecordRM3060
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),editDispositionDateJson(),nonElRecordNameNodeRef);
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),editDispositionDateJson(),elRecordNameNodeRef);
// destroy nonElectronicRecordRM3060 & electronicRecordRM3060 records
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","destroy"),nonElRecordNameNodeRef);
recordFoldersAPI.postRecordAction(getAdminUser().getUsername(),
getAdminUser().getPassword(),new JSONObject().put("name","destroy"),elRecordNameNodeRef);
// check the file is not displayed
assertNull("The file " + nonElectronicRecordRM3060 + " has not been successfully destroyed.", secondNonElectronicRecord.getContent());
assertNull("The file " + electronicRecordRM3060 + " has not been successfully destroyed.", firstElectronicRecord.getContent());
// delete precondition
deleteRecordCategory(recordCategory.getId());
deleteRecordCategory(categorySecondId);
}
private String copyCategory(UserModel user, String categoryId, String copyName) {
RepoTestModel repoTestModel = new RepoTestModel() {};
repoTestModel.setNodeRef(categoryId);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>20.114</version>
<version>20.140-SNAPSHOT</version>
</parent>
<dependencies>
@@ -137,6 +137,10 @@
<artifactId>commons-dbcp2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2023 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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/>.
*/
package org.alfresco.httpclient;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
public class HttpClient4Factory
{
protected static final String TLS_PROTOCOL = "TLS";
protected static final String HTTPS_PROTOCOL = "https";
protected static final String HTTP_TARGET_HOST = "http.target_host";
protected static final String TLS_V_1_2 = "TLSv1.2";
protected static final String TLS_V_1_3 = "TLSv1.3";
private static SSLContext createSSLContext(HttpClientConfig config)
{
KeyManager[] keyManagers = config.getKeyStore().createKeyManagers();
TrustManager[] trustManagers = config.getTrustStore().createTrustManagers();
try
{
SSLContext sslcontext = SSLContext.getInstance(TLS_PROTOCOL);
sslcontext.init(keyManagers, trustManagers, null);
return sslcontext;
}
catch(Throwable e)
{
throw new AlfrescoRuntimeException("Unable to create SSL context", e);
}
}
public static CloseableHttpClient createHttpClient(HttpClientConfig config)
{
return createHttpClient(config, null);
}
public static CloseableHttpClient createHttpClient(HttpClientConfig config, HttpClientConnectionManager connectionManager)
{
HttpClientBuilder clientBuilder = HttpClients.custom();
if(config.isMTLSEnabled())
{
clientBuilder.addInterceptorFirst((HttpRequestInterceptor) (request, context) -> {
if (!((HttpHost) context.getAttribute(HTTP_TARGET_HOST)).getSchemeName().equals(HTTPS_PROTOCOL))
{
String msg = "mTLS is enabled but provided URL does not use a secured protocol";
throw new HttpClientException(msg);
}
});
clientBuilder.setSSLSocketFactory(getSslConnectionSocketFactory(config));
}
if (connectionManager != null)
{
clientBuilder.setConnectionManager(connectionManager);
}
else
{
//Setting a connectionManager overrides these properties
clientBuilder.setMaxConnTotal(config.getMaxTotalConnections());
clientBuilder.setMaxConnPerRoute(config.getMaxHostConnections());
}
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(config.getConnectionTimeout())
.setSocketTimeout(config.getSocketTimeout())
.setConnectionRequestTimeout(config.getConnectionRequestTimeout())
.build();
clientBuilder.setDefaultRequestConfig(requestConfig);
clientBuilder.setRetryHandler(new StandardHttpRequestRetryHandler(5, false));
return clientBuilder.build();
}
private static SSLConnectionSocketFactory getSslConnectionSocketFactory(HttpClientConfig config)
{
return new SSLConnectionSocketFactory(
createSSLContext(config),
new String[] { TLS_V_1_2, TLS_V_1_3 },
null,
config.isHostnameVerificationDisabled() ? new NoopHostnameVerifier() : SSLConnectionSocketFactory.getDefaultHostnameVerifier());
}
public static PoolingHttpClientConnectionManager createPoolingConnectionManager(HttpClientConfig config)
{
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager;
if(config.isMTLSEnabled())
{
poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("https", getSslConnectionSocketFactory(config))
.build());
}
else
{
poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.build());
}
poolingHttpClientConnectionManager.setMaxTotal(config.getMaxTotalConnections());
poolingHttpClientConnectionManager.setDefaultMaxPerRoute(config.getMaxHostConnections());
return poolingHttpClientConnectionManager;
}
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright (C) 2023 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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/>.
*/
package org.alfresco.httpclient;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import org.alfresco.encryption.AlfrescoKeyStore;
import org.alfresco.encryption.AlfrescoKeyStoreImpl;
import org.alfresco.encryption.KeyResourceLoader;
import org.alfresco.encryption.ssl.SSLEncryptionParameters;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class HttpClientConfig
{
private static final String HTTPCLIENT_CONFIG = "httpclient.config.";
protected static final Log LOGGER = LogFactory.getLog(HttpClientConfig.class);
private Properties properties;
private String serviceName;
private SSLEncryptionParameters sslEncryptionParameters;
private KeyResourceLoader keyResourceLoader;
private AlfrescoKeyStore keyStore;
private AlfrescoKeyStore trustStore;
private Map<String, String> config;
public void setProperties(Properties properties)
{
this.properties = properties;
}
public void setServiceName(String serviceName)
{
this.serviceName = serviceName;
}
public void setSslEncryptionParameters(SSLEncryptionParameters sslEncryptionParameters)
{
this.sslEncryptionParameters = sslEncryptionParameters;
}
public void setKeyResourceLoader(KeyResourceLoader keyResourceLoader)
{
this.keyResourceLoader = keyResourceLoader;
}
public AlfrescoKeyStore getKeyStore()
{
return keyStore;
}
public AlfrescoKeyStore getTrustStore()
{
return trustStore;
}
public void init()
{
this.keyStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getKeyStoreParameters(), keyResourceLoader);
this.trustStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getTrustStoreParameters(), keyResourceLoader);
config = retrieveConfig(serviceName);
checkUnsupportedProperties(config);
}
/**
* Method used for retrieving HttpClient config from Global Properties
* @param serviceName name of used service
* @return map of properties
*/
private Map<String, String> retrieveConfig(String serviceName)
{
return properties.keySet().stream()
.filter(key -> key instanceof String)
.map(Object::toString)
.filter(key -> key.startsWith(HTTPCLIENT_CONFIG + serviceName))
.collect(Collectors.toMap(
key -> key.replace(HTTPCLIENT_CONFIG + serviceName + ".", ""),
key -> properties.getProperty(key, null)));
}
private void checkUnsupportedProperties(Map<String, String> config)
{
config.keySet().stream()
.filter(propertyName -> !HttpClientPropertiesEnum.isPropertyNameSupported(propertyName))
.forEach(propertyName -> LOGGER.warn(String.format("For service [%s], an unsupported property [%s] is set", serviceName, propertyName)));
}
private Integer getIntegerProperty(HttpClientPropertiesEnum property)
{
return Integer.parseInt(extractValueFromConfig(property).orElse("0"));
}
private Boolean getBooleanProperty(HttpClientPropertiesEnum property)
{
return Boolean.parseBoolean(extractValueFromConfig(property).orElse("false"));
}
private Optional<String> extractValueFromConfig(HttpClientPropertiesEnum property)
{
Optional<String> optionalProperty = Optional.ofNullable(config.get(property.name));
if(property.isRequired && optionalProperty.isEmpty())
{
String msg = String.format("Required property: '%s' is empty.", property.name);
throw new HttpClientException(msg);
}
return optionalProperty;
}
public Integer getConnectionTimeout()
{
return getIntegerProperty(HttpClientPropertiesEnum.CONNECTION_REQUEST_TIMEOUT);
}
public Integer getSocketTimeout()
{
return getIntegerProperty(HttpClientPropertiesEnum.SOCKET_TIMEOUT);
}
public Integer getConnectionRequestTimeout()
{
return getIntegerProperty(HttpClientPropertiesEnum.CONNECTION_REQUEST_TIMEOUT);
}
public Integer getMaxTotalConnections()
{
return getIntegerProperty(HttpClientPropertiesEnum.MAX_TOTAL_CONNECTIONS);
}
public Integer getMaxHostConnections()
{
return getIntegerProperty(HttpClientPropertiesEnum.MAX_HOST_CONNECTIONS);
}
public Boolean isMTLSEnabled()
{
return getBooleanProperty(HttpClientPropertiesEnum.MTLS_ENABLED);
}
public boolean isHostnameVerificationDisabled()
{
return getBooleanProperty(HttpClientPropertiesEnum.HOSTNAME_VERIFICATION_DISABLED);
}
private enum HttpClientPropertiesEnum
{
CONNECTION_TIMEOUT("connectionTimeout", true),
SOCKET_TIMEOUT("socketTimeout", true),
CONNECTION_REQUEST_TIMEOUT("connectionRequestTimeout", true),
MAX_TOTAL_CONNECTIONS("maxTotalConnections", true),
MAX_HOST_CONNECTIONS("maxHostConnections", true),
HOSTNAME_VERIFICATION_DISABLED("hostnameVerificationDisabled", false),
MTLS_ENABLED("mTLSEnabled", true);
private final String name;
private final Boolean isRequired;
HttpClientPropertiesEnum(String propertyName, Boolean isRequired)
{
this.name = propertyName;
this.isRequired = isRequired;
}
private static final List<String> supportedProperties = new ArrayList<>();
static {
for (HttpClientPropertiesEnum property : HttpClientPropertiesEnum.values()) {
supportedProperties.add(property.name);
}
}
public static boolean isPropertyNameSupported(String propertyName) {
return supportedProperties.contains(propertyName);
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2023 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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/>.
*/
package org.alfresco.httpclient;
import org.alfresco.error.AlfrescoRuntimeException;
public class HttpClientException extends AlfrescoRuntimeException
{
public HttpClientException(String msgId)
{
super(msgId);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,8 +4,6 @@ import org.alfresco.utility.data.AisToken;
import org.alfresco.utility.data.auth.DataAIS;
import org.alfresco.utility.model.UserModel;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.keycloak.representations.AccessTokenResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -86,9 +84,9 @@ public class AuthParameterProviderFactory
parameters.put(SessionParameter.OAUTH_REFRESH_TOKEN, aisToken.getRefreshToken());
parameters.put(SessionParameter.OAUTH_EXPIRATION_TIMESTAMP, String.valueOf(System.currentTimeMillis()
+ (aisToken.getExpiresIn() * 1000))); // getExpiresIn is in seconds
parameters.put(SessionParameter.OAUTH_TOKEN_ENDPOINT, cmisProperties.aisProperty().getAdapterConfig().getAuthServerUrl()
parameters.put(SessionParameter.OAUTH_TOKEN_ENDPOINT, cmisProperties.aisProperty().getAuthServerUrl()
+ "/realms/alfresco/protocol/openid-connect/token");
parameters.put(SessionParameter.OAUTH_CLIENT_ID, cmisProperties.aisProperty().getAdapterConfig().getResource());
parameters.put(SessionParameter.OAUTH_CLIENT_ID, cmisProperties.aisProperty().getResource());
return parameters;
}
@@ -110,10 +108,10 @@ public class AuthParameterProviderFactory
// Attempt to get an access token for userModel from AIS
aisToken = dataAIS.perform().getAccessToken(userModel);
}
catch (HttpResponseException e)
catch (AssertionError e)
{
// Trying to authenticate with invalid user credentials so return an invalid access token
if (e.getStatusCode() == 401)
if (e.getMessage().contains("invalid_grant"))
{
STEP(String.format("%s Invalid user credentials were provided %s:%s. Using invalid token for reqest.",
STEP_PREFIX, userModel.getUsername(), userModel.getPassword()));

View File

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

View File

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

View File

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

View File

@@ -30,7 +30,6 @@ import static org.alfresco.utility.report.log.Step.STEP;
import org.alfresco.utility.data.AisToken;
import org.alfresco.utility.data.auth.DataAIS;
import org.alfresco.utility.model.UserModel;
import org.keycloak.authorization.client.util.HttpResponseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -86,12 +85,11 @@ public class RestAisAuthentication
// Attempt to get an access token for userModel from AIS
aisToken = dataAIS.perform().getAccessToken(userModel);
}
catch (HttpResponseException e)
catch (AssertionError e)
{
// Trying to authenticate with invalid user credentials or disabled
// user so return an invalid access token
String httpResponse = new String(e.getBytes());
if (e.getStatusCode() == 401 || httpResponse.contains(USER_DISABLED_MSG))
if (e.getMessage().contains("invalid_grant"))
{
STEP(String.format("%s User disabled or invalid user credentials were provided %s:%s. Using invalid token for request.", STEP_PREFIX,
userModel.getUsername(), userModel.getPassword()));

View File

@@ -680,17 +680,22 @@ public class RestWrapper extends DSLWrapper<RestWrapper>
}
else
{
if (returnedResponse.getContentType().contains("image/png"))
{
LOG.info("On {} {}, received the response with an image and headers: \n{}", restRequest.getHttpMethod(), restRequest.getPath(),
returnedResponse.getHeaders().toString());
}
else if (returnedResponse.getContentType().contains("application/json") && !returnedResponse.asString().isEmpty())
if (returnedResponse.asString().isEmpty())
{
LOG.info("On {} {}, received the following response \n{}", restRequest.getHttpMethod(), restRequest.getPath(),
Utility.prettyPrintJsonString(returnedResponse.asString()));
returnedResponse.getStatusCode());
}
else if (returnedResponse.getContentType().contains("application/xml") && !returnedResponse.asString().isEmpty())
else if (returnedResponse.getContentType().contains("image/png"))
{
LOG.info("On {} {}, received the response with an image and headers: \n{}", restRequest.getHttpMethod(), restRequest.getPath(),
returnedResponse.getHeaders().toString());
}
else if (returnedResponse.getContentType().contains("application/json"))
{
LOG.info("On {} {}, received the following response \n{}", restRequest.getHttpMethod(), restRequest.getPath(),
Utility.prettyPrintJsonString(returnedResponse.asString()));
}
else if (returnedResponse.getContentType().contains("application/xml"))
{
String response = parseXML(returnedResponse);
LOG.info("On {} {}, received the following response \n{}", restRequest.getHttpMethod(), restRequest.getPath(), response);
@@ -698,7 +703,7 @@ public class RestWrapper extends DSLWrapper<RestWrapper>
else
{
LOG.info("On {} {}, received the following response \n{}", restRequest.getHttpMethod(), restRequest.getPath(),
ToStringBuilder.reflectionToString(returnedResponse.asString(), ToStringStyle.MULTI_LINE_STYLE));
ToStringBuilder.reflectionToString(returnedResponse.asString(), ToStringStyle.MULTI_LINE_STYLE));
}
}
}

View File

@@ -60,7 +60,7 @@ public class RestErrorModel
public static String INVALID_MAXITEMS = "Invalid paging parameter maxItems:%s";
public static String INVALID_SKIPCOUNT = "Invalid paging parameter skipCount:%s";
public static String INVALID_TAG = "Tag name must not contain %s char sequence";
public static String EMPTY_TAG = "New tag cannot be null";
public static String BLANK_TAG = "New tag cannot be blank";
public static String UNKNOWN_ROLE = "Unknown role '%s'";
public static String ALREADY_Site_MEMBER = "%s is already a member of site %s";
public static String ALREADY_INVITED = "%s is already invited to site %s";

View File

@@ -1,5 +1,10 @@
package org.alfresco.rest.tags;
import static org.alfresco.utility.data.RandomData.getRandomName;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.springframework.http.HttpStatus.CREATED;
import static org.springframework.http.HttpStatus.OK;
import org.alfresco.rest.model.RestErrorModel;
import org.alfresco.rest.model.RestTagModel;
import org.alfresco.utility.constants.UserRole;
@@ -14,6 +19,11 @@ import org.testng.annotations.Test;
public class GetTagTests extends TagsDataPrep
{
private static final String FIELD_ID = "id";
private static final String FIELD_TAG = "tag";
private static final String FIELD_COUNT = "count";
private static final String TAG_NAME_PREFIX = "tag-name";
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify admin user gets tag using REST API and status code is OK (200)")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsAbleToGetTag() throws Exception
@@ -128,4 +138,25 @@ public class GetTagTests extends TagsDataPrep
.descriptionURLIs(RestErrorModel.RESTAPIEXPLORER)
.stackTraceIs(RestErrorModel.STACKTRACE);
}
}
/**
* Verify that count field is not present for searched tag.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
public void testGetTag_notIncludingCount()
{
STEP("Create single tag as admin");
final RestTagModel tagModel = createTagModelWithName(getRandomName(TAG_NAME_PREFIX).toLowerCase());
final RestTagModel createdTag = restClient.authenticateUser(adminUserModel).withCoreAPI().createSingleTag(tagModel);
restClient.assertStatusCodeIs(CREATED);
STEP("Get a single tag, not including count and verify if it is not present in the response");
final RestTagModel searchedTag = restClient.withCoreAPI().getTag(createdTag);
restClient.assertStatusCodeIs(OK);
searchedTag.assertThat().field(FIELD_TAG).is(tagModel.getTag())
.assertThat().field(FIELD_ID).isNotEmpty()
.assertThat().field(FIELD_COUNT).isNull();
}
}

View File

@@ -1,6 +1,8 @@
package org.alfresco.rest.tags;
import static org.alfresco.utility.data.RandomData.getRandomName;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.springframework.http.HttpStatus.OK;
import java.util.Set;
@@ -8,7 +10,6 @@ import org.alfresco.rest.model.RestErrorModel;
import org.alfresco.rest.model.RestTagModel;
import org.alfresco.rest.model.RestTagModelsCollection;
import org.alfresco.utility.constants.UserRole;
import org.alfresco.utility.data.RandomData;
import org.alfresco.utility.model.TestGroup;
import org.alfresco.utility.testrail.ExecutionType;
import org.alfresco.utility.testrail.annotation.TestRail;
@@ -18,58 +19,63 @@ import org.testng.annotations.Test;
@Test(groups = {TestGroup.REQUIRE_SOLR})
public class GetTagsTests extends TagsDataPrep
{
private static final String FIELD_ID = "id";
private static final String FIELD_TAG = "tag";
private static final String FIELD_COUNT = "count";
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Verify user with Manager role gets tags using REST API and status code is OK (200)")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
public void getTagsWithManagerRole() throws Exception
public void getTagsWithManagerRole()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager));
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
returnedCollection.assertThat().entriesListIsNotEmpty()
.and().entriesListContains("tag", documentTagValue.toLowerCase())
.and().entriesListContains("tag", documentTagValue2.toLowerCase());
.and().entriesListContains("tag", documentTagValue)
.and().entriesListContains("tag", documentTagValue2);
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify user with Collaborator role gets tags using REST API and status code is OK (200)")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void getTagsWithCollaboratorRole() throws Exception
public void getTagsWithCollaboratorRole()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteCollaborator));
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
returnedCollection.assertThat().entriesListIsNotEmpty()
.and().entriesListContains("tag", documentTagValue.toLowerCase())
.and().entriesListContains("tag", documentTagValue2.toLowerCase());
.and().entriesListContains("tag", documentTagValue)
.and().entriesListContains("tag", documentTagValue2);
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify user with Contributor role gets tags using REST API and status code is OK (200)")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void getTagsWithContributorRole() throws Exception
public void getTagsWithContributorRole()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteContributor));
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
returnedCollection.assertThat().entriesListIsNotEmpty()
.and().entriesListContains("tag", documentTagValue.toLowerCase())
.and().entriesListContains("tag", documentTagValue2.toLowerCase());
.and().entriesListContains("tag", documentTagValue)
.and().entriesListContains("tag", documentTagValue2);
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify user with Consumer role gets tags using REST API and status code is OK (200)")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void getTagsWithConsumerRole() throws Exception
public void getTagsWithConsumerRole()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteConsumer));
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
returnedCollection.assertThat().entriesListIsNotEmpty()
.and().entriesListContains("tag", documentTagValue.toLowerCase())
.and().entriesListContains("tag", documentTagValue2.toLowerCase());
.and().entriesListContains("tag", documentTagValue)
.and().entriesListContains("tag", documentTagValue2);
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Failed authentication get tags call returns status code 401 with Manager role")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
// @Bug(id="MNT-16904", description = "It fails only on environment with tenants")
public void failedAuthenticationReturnsUnauthorizedStatus() throws Exception
public void failedAuthenticationReturnsUnauthorizedStatus()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager));
userModel = dataUser.createRandomTestUser();
@@ -83,7 +89,7 @@ public class GetTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that if maxItems is invalid status code returned is 400")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
public void maxItemsInvalidValueTest() throws Exception
public void maxItemsInvalidValueTest()
{
restClient.authenticateUser(adminUserModel).withParams("maxItems=abc").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError().containsSummary(String.format(RestErrorModel.INVALID_MAXITEMS, "abc"));
@@ -92,7 +98,7 @@ public class GetTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that if skipCount is invalid status code returned is 400")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
public void skipCountInvalidValueTest() throws Exception
public void skipCountInvalidValueTest()
{
restClient.authenticateUser(adminUserModel).withParams("skipCount=abc").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST).assertLastError().containsSummary(String.format(RestErrorModel.INVALID_SKIPCOUNT, "abc"));
@@ -101,55 +107,55 @@ public class GetTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that file tag is retrieved")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
public void fileTagIsRetrieved() throws Exception
public void fileTagIsRetrieved()
{
restClient.authenticateUser(adminUserModel);
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
returnedCollection.assertThat().entriesListIsNotEmpty()
.and().entriesListContains("tag", documentTagValue.toLowerCase())
.and().entriesListContains("tag", documentTagValue2.toLowerCase());
.and().entriesListContains("tag", documentTagValue)
.and().entriesListContains("tag", documentTagValue2);
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that folder tag is retrieved")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
public void folderTagIsRetrieved() throws Exception
public void folderTagIsRetrieved()
{
restClient.authenticateUser(adminUserModel);
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
returnedCollection.assertThat().entriesListIsNotEmpty()
.and().entriesListContains("tag", folderTagValue.toLowerCase());
.and().entriesListContains("tag", folderTagValue);
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify site Manager is able to get tags using properties parameter."
+ "Check that properties filter is applied.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void siteManagerIsAbleToRetrieveTagsWithPropertiesParameter() throws Exception
public void siteManagerIsAbleToRetrieveTagsWithPropertiesParameter()
{
returnedCollection = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
.withParams("maxItems=5000&properties=tag").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
returnedCollection.assertThat().entriesListIsNotEmpty()
.and().entriesListContains("tag", documentTagValue.toLowerCase())
.and().entriesListContains("tag", documentTagValue2.toLowerCase())
.and().entriesListContains("tag", documentTagValue)
.and().entriesListContains("tag", documentTagValue2)
.and().entriesListDoesNotContain("id");
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "With admin get tags and use skipCount parameter. Check pagination")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void useSkipCountCheckPagination() throws Exception
public void useSkipCountCheckPagination()
{
returnedCollection = restClient.authenticateUser(adminUserModel).withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
RestTagModel firstTag = returnedCollection.getEntries().get(0).onModel();
RestTagModel secondTag = returnedCollection.getEntries().get(1).onModel();
RestTagModelsCollection tagsWithSkipCount = restClient.withParams("skipCount=2").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
tagsWithSkipCount.assertThat().entriesListDoesNotContain("tag", firstTag.getTag())
.assertThat().entriesListDoesNotContain("tag", secondTag.getTag());
@@ -159,15 +165,15 @@ public class GetTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "With admin get tags and use maxItems parameter. Check pagination")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void useMaxItemsParameterCheckPagination() throws Exception
public void useMaxItemsParameterCheckPagination()
{
returnedCollection = restClient.authenticateUser(adminUserModel).withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
RestTagModel firstTag = returnedCollection.getEntries().get(0).onModel();
RestTagModel secondTag = returnedCollection.getEntries().get(1).onModel();
RestTagModelsCollection tagsWithMaxItems = restClient.withParams("maxItems=2").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
tagsWithMaxItems.assertThat().entriesListContains("tag", firstTag.getTag())
.assertThat().entriesListContains("tag", secondTag.getTag())
@@ -178,11 +184,11 @@ public class GetTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "With manager get tags and use high skipCount parameter. Check pagination")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void useHighSkipCountCheckPagination() throws Exception
public void useHighSkipCountCheckPagination()
{
returnedCollection = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
.withParams("skipCount=20000").withCoreAPI().getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
restClient.assertStatusCodeIs(OK);
returnedCollection.assertThat().entriesListIsEmpty()
.getPagination().assertThat().field("maxItems").is(100)
.and().field("hasMoreItems").is("false")
@@ -194,7 +200,7 @@ public class GetTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "With Collaborator user get tags and use maxItems with value zero. Check default error model schema")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void useMaxItemsWithValueZeroCheckDefaultErrorModelSchema() throws Exception
public void useMaxItemsWithValueZeroCheckDefaultErrorModelSchema()
{
returnedCollection = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteCollaborator))
.withParams("maxItems=0").withCoreAPI().getTags();
@@ -208,9 +214,9 @@ public class GetTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "With Manager user delete tag. Check it is not retrieved anymore.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void checkThatDeletedTagIsNotRetrievedAnymore() throws Exception
public void checkThatDeletedTagIsNotRetrievedAnymore()
{
String removedTag = RandomData.getRandomName("tag3");
String removedTag = getRandomName("tag3");
RestTagModel deletedTag = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
.withCoreAPI().usingResource(document).addTag(removedTag);
@@ -220,7 +226,7 @@ public class GetTagsTests extends TagsDataPrep
returnedCollection = restClient.withParams("maxItems=10000").withCoreAPI().getTags();
returnedCollection.assertThat().entriesListIsNotEmpty()
.and().entriesListDoesNotContain("tag", removedTag.toLowerCase());
.and().entriesListDoesNotContain("tag", removedTag);
}
/**
@@ -237,7 +243,7 @@ public class GetTagsTests extends TagsDataPrep
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetMatches("tag", Set.of(documentTagValue.toLowerCase()));
.entrySetMatches("tag", Set.of(documentTagValue));
}
/**
@@ -254,7 +260,7 @@ public class GetTagsTests extends TagsDataPrep
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetMatches("tag", Set.of(documentTagValue.toLowerCase(), folderTagValue.toLowerCase()));
.entrySetMatches("tag", Set.of(documentTagValue, folderTagValue));
}
/**
@@ -265,13 +271,13 @@ public class GetTagsTests extends TagsDataPrep
{
STEP("Get tags with names filter using MATCHES and expect one item in result");
returnedCollection = restClient.authenticateUser(adminUserModel)
.withParams("where=(tag MATCHES ('orphan*'))")
.withCoreAPI()
.getTags();
.withParams("where=(tag MATCHES ('orphan*'))", "maxItems=10000")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetContains("tag", orphanTag.getTag().toLowerCase());
.entrySetContains("tag", orphanTag.getTag());
}
/**
@@ -282,13 +288,13 @@ public class GetTagsTests extends TagsDataPrep
{
STEP("Get tags with names filter using EQUALS and MATCHES and expect four items in result");
returnedCollection = restClient.authenticateUser(adminUserModel)
.withParams("where=(tag='" + orphanTag.getTag() + "' OR tag MATCHES ('*tag*'))")
.withParams("where=(tag MATCHES ('*tag*'))", "maxItems=10000")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetContains("tag", documentTagValue.toLowerCase(), documentTagValue2.toLowerCase(), folderTagValue.toLowerCase(), orphanTag.getTag().toLowerCase());
.entrySetContains("tag", documentTagValue, documentTagValue2, folderTagValue, orphanTag.getTag());
}
/**
@@ -299,17 +305,17 @@ public class GetTagsTests extends TagsDataPrep
{
STEP("Get tags applying names filter using MATCHES twice and expect four items in result");
returnedCollection = restClient.authenticateUser(adminUserModel)
.withParams("where=(tag MATCHES ('orphan*') OR tag MATCHES ('tag*'))")
.withParams("where=(tag MATCHES ('orphan*') OR tag MATCHES ('tag*'))", "maxItems=10000")
.withCoreAPI()
.getTags();
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedCollection.assertThat()
.entrySetContains("tag", documentTagValue.toLowerCase(), documentTagValue2.toLowerCase(), folderTagValue.toLowerCase(), orphanTag.getTag().toLowerCase());
.entrySetContains("tag", documentTagValue, documentTagValue2, folderTagValue, orphanTag.getTag());
}
/**
* Verify that providing incorrect field name in where query will result with 400 (Bad Request).
* Verify that providing incorrect field name in where query will result with 400 (Bad Request).
*/
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void testGetTags_withWrongWherePropertyNameAndExpect400()
@@ -339,4 +345,36 @@ public class GetTagsTests extends TagsDataPrep
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
.assertLastError().containsSummary("An invalid WHERE query was received. Unsupported Predicate");
}
/**
* Verify if count field is present for searched tags.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
public void testGetTags_includingCount()
{
STEP("Get tags including count and verify if it is present in the response");
final RestTagModelsCollection searchedTags = restClient.withCoreAPI().include(FIELD_COUNT).getTags();
restClient.assertStatusCodeIs(OK);
searchedTags.assertThat().entriesListIsNotEmpty()
.assertThat().entriesListContains(FIELD_COUNT)
.assertThat().entriesListContains(FIELD_TAG)
.assertThat().entriesListContains(FIELD_ID);
}
/**
* Verify if count field is not present for searched tags.
*/
@Test(groups = {TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
public void testGetTags_notIncludingCount()
{
STEP("Get tags, not including count and verify if it is not in the response");
final RestTagModelsCollection searchedTags = restClient.withCoreAPI().getTags();
restClient.assertStatusCodeIs(OK);
searchedTags.assertThat().entriesListIsNotEmpty()
.assertThat().entriesListDoesNotContain(FIELD_COUNT)
.assertThat().entriesListContains(FIELD_TAG)
.assertThat().entriesListContains(FIELD_ID);
}
}

View File

@@ -39,24 +39,25 @@ public class TagsDataPrep extends RestTest
document = dataContent.usingUser(adminUserModel).usingSite(siteModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
folder = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
documentTagValue = RandomData.getRandomName("tag");
documentTagValue2 = RandomData.getRandomName("tag");
folderTagValue = RandomData.getRandomName("tag");
documentTagValue = RandomData.getRandomName("tag").toLowerCase();
documentTagValue2 = RandomData.getRandomName("tag").toLowerCase();
folderTagValue = RandomData.getRandomName("tag").toLowerCase();
restClient.authenticateUser(adminUserModel);
documentTag = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue);
documentTag2 = restClient.withCoreAPI().usingResource(document).addTag(documentTagValue2);
folderTag = restClient.withCoreAPI().usingResource(folder).addTag(folderTagValue);
orphanTag = restClient.withCoreAPI().createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("orphan-tag")).create());
orphanTag = restClient.withCoreAPI().createSingleTag(RestTagModel.builder().tag(RandomData.getRandomName("orphan-tag").toLowerCase()).create());
// Allow indexing to complete.
Utility.sleep(500, 60000, () ->
{
returnedCollection = restClient.withParams("maxItems=10000", "where=(tag MATCHES ('*tag*'))")
.withCoreAPI().getTags();
returnedCollection.assertThat().entriesListContains("tag", documentTagValue.toLowerCase())
.and().entriesListContains("tag", documentTagValue2.toLowerCase())
.and().entriesListContains("tag", folderTagValue.toLowerCase());
.withCoreAPI().getTags();
returnedCollection.assertThat().entriesListContains("tag", documentTagValue)
.and().entriesListContains("tag", documentTagValue2)
.and().entriesListContains("tag", folderTagValue)
.and().entriesListContains("tag", orphanTag.getTag());
});
}
@@ -78,4 +79,9 @@ public class TagsDataPrep extends RestTest
.tag(tag)
.create();
}
protected RestTagModel createTagModelWithName(final String tagName)
{
return RestTagModel.builder().tag(tagName).create();
}
}

View File

@@ -1,5 +1,8 @@
package org.alfresco.rest.tags;
import static org.alfresco.utility.report.log.Step.STEP;
import static org.testng.Assert.fail;
import org.alfresco.rest.model.RestErrorModel;
import org.alfresco.rest.model.RestTagModel;
import org.alfresco.utility.Utility;
@@ -13,6 +16,7 @@ import org.alfresco.utility.testrail.annotation.TestRail;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.http.HttpStatus;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;
/**
@@ -25,28 +29,29 @@ public class UpdateTagTests extends TagsDataPrep
private String randomTag = "";
@BeforeMethod(alwaysRun=true)
public void addTagToDocument() throws Exception
public void addTagToDocument()
{
restClient.authenticateUser(adminUserModel);
oldTag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("old"));
randomTag = RandomData.getRandomName("tag");
oldTag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("old").toLowerCase());
randomTag = RandomData.getRandomName("tag").toLowerCase();
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY, description = "Verify Admin user updates tags and status code is 200")
@Bug(id="REPO-1828")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
public void adminIsAbleToUpdateTags() throws Exception
public void adminIsAbleToUpdateTags()
{
restClient.authenticateUser(adminUserModel);
returnedModel = restClient.withCoreAPI().usingTag(oldTag).update(randomTag);
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedModel.assertThat().field("tag").is(randomTag);
returnedModel.assertThat().field("id").isNotNull();
}
@TestRail(section = { TestGroup.REST_API,
TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify Manager user can't update tags with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void managerIsNotAbleToUpdateTag() throws Exception
public void managerIsNotAbleToUpdateTag()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager));
restClient.withCoreAPI().usingTag(oldTag).update(randomTag);
@@ -56,7 +61,7 @@ public class UpdateTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
executionType = ExecutionType.REGRESSION, description = "Verify Collaborator user can't update tags with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void collaboratorIsNotAbleToUpdateTagCheckDefaultErrorModelSchema() throws Exception
public void collaboratorIsNotAbleToUpdateTagCheckDefaultErrorModelSchema()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteCollaborator));
restClient.withCoreAPI().usingTag(oldTag).update(randomTag);
@@ -69,7 +74,7 @@ public class UpdateTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
executionType = ExecutionType.REGRESSION, description = "Verify Contributor user can't update tags with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void contributorIsNotAbleToUpdateTag() throws Exception
public void contributorIsNotAbleToUpdateTag()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteContributor));
restClient.withCoreAPI().usingTag(oldTag).update(randomTag);
@@ -79,7 +84,7 @@ public class UpdateTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
executionType = ExecutionType.SANITY, description = "Verify Consumer user can't update tags with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void consumerIsNotAbleToUpdateTag() throws Exception
public void consumerIsNotAbleToUpdateTag()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteConsumer));
restClient.withCoreAPI().usingTag(oldTag).update(randomTag);
@@ -90,7 +95,7 @@ public class UpdateTagTests extends TagsDataPrep
executionType = ExecutionType.SANITY, description = "Verify user gets status code 401 if authentication call fails")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
// @Bug(id="MNT-16904", description = "It fails only on environment with tenants")
public void userIsNotAbleToUpdateTagIfAuthenticationFails() throws Exception
public void userIsNotAbleToUpdateTagIfAuthenticationFails()
{
UserModel siteManager = usersWithRoles.getOneUserWithRole(UserRole.SiteManager);
String managerPassword = siteManager.getPassword();
@@ -103,118 +108,140 @@ public class UpdateTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify admin is not able to update tag with invalid id")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsNotAbleToUpdateTagWithInvalidId() throws Exception
public void adminIsNotAbleToUpdateTagWithInvalidId()
{
String invalidTagId = "invalid-id";
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag").toLowerCase());
tag.setId(invalidTagId);
restClient.withCoreAPI().usingTag(tag).update(RandomData.getRandomName("tag"));
restClient.withCoreAPI().usingTag(tag).update(RandomData.getRandomName("tag").toLowerCase());
restClient.assertStatusCodeIs(HttpStatus.NOT_FOUND)
.assertLastError().containsSummary(String.format(RestErrorModel.ENTITY_NOT_FOUND, invalidTagId));
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify admin is not able to update tag with empty id")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsNotAbleToUpdateTagWithEmptyId() throws Exception
public void adminIsNotAbleToUpdateTagWithEmptyId()
{
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag").toLowerCase());
tag.setId("");
restClient.withCoreAPI().usingTag(tag).update(RandomData.getRandomName("tag"));
restClient.withCoreAPI().usingTag(tag).update(RandomData.getRandomName("tag").toLowerCase());
restClient.assertStatusCodeIs(HttpStatus.METHOD_NOT_ALLOWED)
.assertLastError().containsSummary(RestErrorModel.PUT_EMPTY_ARGUMENT);
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify admin is not able to update tag with invalid body")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsNotAbleToUpdateTagWithEmptyBody() throws Exception
public void adminIsNotAbleToUpdateTagWithEmptyBody()
{
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag").toLowerCase());
restClient.withCoreAPI().usingTag(tag).update("");
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
.assertLastError().containsSummary(RestErrorModel.EMPTY_TAG);
.assertLastError().containsSummary(RestErrorModel.BLANK_TAG);
}
@Bug(id="ACE-5629")
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify admin is not able to update tag with invalid body containing '|' symbol")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsNotAbleToUpdateTagWithInvalidBodyScenario1() throws Exception
@Ignore
public void adminIsNotAbleToUpdateTagWithInvalidBodyScenario1()
{
String invalidTagBody = "|.\"/<>*";
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
Utility.sleep(500, 20000, () ->
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag").toLowerCase());
try
{
restClient.withCoreAPI().usingTag(tag).update(invalidTagBody);
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
.assertLastError().containsSummary(String.format(RestErrorModel.INVALID_TAG, invalidTagBody));
});
Utility.sleep(500, 20000, () ->
{
restClient.withCoreAPI().usingTag(tag).update(invalidTagBody);
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
.assertLastError().containsSummary(String.format(RestErrorModel.INVALID_TAG, invalidTagBody));
});
}
catch (InterruptedException e)
{
fail("Test interrupted while waiting for error status code.");
}
}
@Bug(id="ACE-5629")
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify admin is not able to update tag with invalid body without '|' symbol")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsNotAbleToUpdateTagWithInvalidBodyScenario2() throws Exception
@Ignore
public void adminIsNotAbleToUpdateTagWithInvalidBodyScenario2()
{
String invalidTagBody = ".\"/<>*";
RestTagModel tag = restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
try
{
Utility.sleep(500, 20000, () ->
{
restClient.withCoreAPI().usingTag(tag).update(invalidTagBody);
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
restClient.withCoreAPI().usingTag(tag).update(invalidTagBody);
restClient.assertStatusCodeIs(HttpStatus.BAD_REQUEST)
.assertLastError().containsSummary(String.format(RestErrorModel.INVALID_TAG, invalidTagBody));
});
});
}
catch (InterruptedException e)
{
fail("Test interrupted while waiting for error status code.");
}
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify admin user can provide large string for new tag value.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
@Bug(id="REPO-1828")
public void adminIsAbleToUpdateTagsProvideLargeStringTag() throws Exception
@Ignore
public void adminIsAbleToUpdateTagsProvideLargeStringTag()
{
String largeStringTag = RandomStringUtils.randomAlphanumeric(10000);
String largeStringTag = RandomStringUtils.randomAlphanumeric(10000).toLowerCase();
restClient.authenticateUser(adminUserModel);
returnedModel = restClient.withCoreAPI().usingTag(oldTag).update(largeStringTag);
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedModel.assertThat().field("tag").is(largeStringTag);
returnedModel.assertThat().field("id").isNotNull();
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify admin user can provide short string for new tag value.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
@Bug(id="REPO-1828")
public void adminIsAbleToUpdateTagsProvideShortStringTag() throws Exception
public void adminIsAbleToUpdateTagsProvideShortStringTag()
{
String shortStringTag = RandomStringUtils.randomAlphanumeric(2);
String shortStringTag = RandomStringUtils.randomAlphanumeric(2).toLowerCase();
restClient.authenticateUser(adminUserModel);
returnedModel = restClient.withCoreAPI().usingTag(oldTag).update(shortStringTag);
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedModel.assertThat().field("tag").is(shortStringTag);
returnedModel.assertThat().field("id").isNotNull();
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify admin user can provide string with special chars for new tag value.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
@Bug(id="REPO-1828")
public void adminIsAbleToUpdateTagsProvideSpecialCharsStringTag() throws Exception
@Ignore
public void adminIsAbleToUpdateTagsProvideSpecialCharsStringTag()
{
String specialCharsString = "!@#$%^&*()'\".,<>-_+=|\\";
String specialCharsString = RandomData.getRandomName("!@#$%^&*()'\".,<>-_+=|\\").toLowerCase();
restClient.authenticateUser(adminUserModel);
returnedModel = restClient.withCoreAPI().usingTag(oldTag).update(specialCharsString);
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedModel.assertThat().field("tag").is(specialCharsString);
returnedModel.assertThat().field("id").isNotNull();
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Admin user can provide existing tag for new tag value.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
@Bug(id="REPO-1828")
public void adminIsAbleToUpdateTagsProvideExistingTag() throws Exception
@Ignore
public void adminIsAbleToUpdateTagsProvideExistingTag()
{
String existingTag = "oldTag";
String existingTag = RandomData.getRandomName("oldTag").toLowerCase();
RestTagModel oldExistingTag = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager))
.withCoreAPI().usingResource(document).addTag(existingTag);
restClient.assertStatusCodeIs(HttpStatus.CREATED);
@@ -223,38 +250,43 @@ public class UpdateTagTests extends TagsDataPrep
returnedModel = restClient.withCoreAPI().usingTag(oldExistingTag).update(existingTag);
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedModel.assertThat().field("tag").is(existingTag);
returnedModel.assertThat().field("id").isNotNull();
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Admin user can delete a tag, add tag and update it.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
@Bug(id="REPO-1828")
public void adminDeleteTagAddTagUpdateTag() throws Exception
@Ignore
public void adminDeleteTagAddTagUpdateTag()
{
restClient.authenticateUser(adminUserModel)
.withCoreAPI().usingResource(document).deleteTag(oldTag);
restClient.assertStatusCodeIs(HttpStatus.NO_CONTENT);
String newTag = "addTag";
String newTag = RandomData.getRandomName("addTag").toLowerCase();
RestTagModel newTagModel = restClient.withCoreAPI().usingResource(document).addTag(newTag);
restClient.assertStatusCodeIs(HttpStatus.CREATED);
returnedModel = restClient.withCoreAPI().usingTag(newTagModel).update(newTag);
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedModel.assertThat().field("tag").is(newTag);
returnedModel.assertThat().field("id").isNotNull();
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Admin user can update a tag, delete tag and add it.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
@Bug(id="REPO-1828")
public void adminUpdateTagDeleteTagAddTag() throws Exception
@Ignore
public void adminUpdateTagDeleteTagAddTag()
{
String newTag = "addTag";
String newTag = RandomData.getRandomName("addTag").toLowerCase();
returnedModel = restClient.authenticateUser(adminUserModel).withCoreAPI().usingTag(oldTag).update(newTag);
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedModel.assertThat().field("tag").is(newTag);
returnedModel.assertThat().field("id").isNotNull();
restClient.withCoreAPI().usingResource(document).deleteTag(returnedModel);
restClient.assertStatusCodeIs(HttpStatus.NO_CONTENT);
@@ -262,4 +294,18 @@ public class UpdateTagTests extends TagsDataPrep
restClient.withCoreAPI().usingResource(document).addTag(newTag);
restClient.assertStatusCodeIs(HttpStatus.CREATED);
}
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY,
description = "Verify Admin user updates orphan tags and status code is 200")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
public void adminIsAbleToUpdateOrphanTag()
{
STEP("Update orphan tag and expect 200");
final String newTagName = RandomData.getRandomName("new").toLowerCase();
returnedModel = restClient.authenticateUser(adminUserModel).withCoreAPI().usingTag(orphanTag).update(newTagName);
restClient.assertStatusCodeIs(HttpStatus.OK);
returnedModel.assertThat().field("tag").is(newTagName);
returnedModel.assertThat().field("id").isNotNull();
}
}

View File

@@ -33,13 +33,13 @@ public class AddTagTests extends TagsDataPrep
@BeforeMethod(alwaysRun = true)
public void generateRandomTag()
{
tagValue = RandomData.getRandomName("tag");
tagValue = RandomData.getRandomName("tag").toLowerCase();
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify admin user adds tags with Rest API and status code is 201")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsAbleToAddTag() throws Exception
public void adminIsAbleToAddTag()
{
restClient.authenticateUser(adminUserModel);
returnedModel = restClient.withCoreAPI().usingResource(document).addTag(tagValue);
@@ -51,7 +51,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY,
description = "Verify Manager user adds tags with Rest API and status code is 201")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
public void managerIsAbleToTagAFile() throws Exception
public void managerIsAbleToTagAFile()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager));
returnedModel = restClient.withCoreAPI().usingResource(document).addTag(tagValue);
@@ -63,7 +63,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Collaborator user adds tags with Rest API and status code is 201")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void collaboratorIsAbleToTagAFile() throws Exception
public void collaboratorIsAbleToTagAFile()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteCollaborator));
returnedModel = restClient.withCoreAPI().usingResource(document).addTag(tagValue);
@@ -75,7 +75,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Contributor user doesn't have permission to add tags with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void contributorIsNotAbleToAddTagToAnotherContent() throws Exception
public void contributorIsNotAbleToAddTagToAnotherContent()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteContributor));
restClient.withCoreAPI().usingResource(document).addTag(tagValue);
@@ -85,7 +85,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Contributor user adds tags to his content with Rest API and status code is 201")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void contributorIsAbleToAddTagToHisContent() throws Exception
public void contributorIsAbleToAddTagToHisContent()
{
userModel = usersWithRoles.getOneUserWithRole(UserRole.SiteContributor);
restClient.authenticateUser(userModel);
@@ -99,7 +99,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Consumer user doesn't have permission to add tags with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void consumerIsNotAbleToTagAFile() throws Exception
public void consumerIsNotAbleToTagAFile()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteConsumer));
restClient.withCoreAPI().usingResource(document).addTag(tagValue);
@@ -110,7 +110,7 @@ public class AddTagTests extends TagsDataPrep
description = "Verify user gets status code 401 if authentication call fails")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
// @Bug(id="MNT-16904", description = "It fails only on environment with tenants")
public void userIsNotAbleToAddTagIfAuthenticationFails() throws Exception
public void userIsNotAbleToAddTagIfAuthenticationFails()
{
UserModel siteManager = usersWithRoles.getOneUserWithRole(UserRole.SiteManager);
String managerPassword = siteManager.getPassword();
@@ -124,7 +124,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that adding empty tag returns status code 400")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void emptyTagTest() throws Exception
public void emptyTagTest()
{
restClient.authenticateUser(adminUserModel);
returnedModel = restClient.withCoreAPI().usingResource(document).addTag("");
@@ -134,7 +134,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that adding tag with user that has no permissions returns status code 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void addTagWithUserThatDoesNotHavePermissions() throws Exception
public void addTagWithUserThatDoesNotHavePermissions()
{
restClient.authenticateUser(dataUser.createRandomTestUser());
returnedModel = restClient.withCoreAPI().usingResource(document).addTag(tagValue);
@@ -144,7 +144,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that adding tag to a node that does not exist returns status code 404")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void addTagToInexistentNode() throws Exception
public void addTagToInexistentNode()
{
String oldNodeRef = document.getNodeRef();
String nodeRef = RandomStringUtils.randomAlphanumeric(10);
@@ -159,7 +159,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY,
description = "Verify that manager is able to tag a folder")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
public void managerIsAbleToTagAFolder() throws Exception
public void managerIsAbleToTagAFolder()
{
FolderModel folderModel = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager));
@@ -171,7 +171,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that tagged file can be tagged again")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void addTagToATaggedFile() throws Exception
public void addTagToATaggedFile()
{
restClient.authenticateUser(adminUserModel);
@@ -195,7 +195,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that user cannot add invalid tag")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void addInvalidTag() throws Exception
public void addInvalidTag()
{
restClient.authenticateUser(adminUserModel);
returnedModel = restClient.withCoreAPI().usingResource(document).addTag("-1~!|@#$%^&*()_=");
@@ -205,7 +205,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that contributor is able to tag a folder created by self")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void contributorIsAbleToTagAFolderCreatedBySelf() throws Exception
public void contributorIsAbleToTagAFolderCreatedBySelf()
{
FolderModel folderModel = dataContent.usingUser(usersWithRoles.getOneUserWithRole(UserRole.SiteContributor)).usingSite(siteModel).createFolder();
@@ -218,7 +218,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that collaborator is able to tag a folder")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void collaboratorIsAbleToTagAFolder() throws Exception
public void collaboratorIsAbleToTagAFolder()
{
FolderModel folderModel = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
@@ -231,7 +231,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that consumer is not able to tag a folder. Check default error model schema.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void consumerIsNotAbleToTagAFolder() throws Exception
public void consumerIsNotAbleToTagAFolder()
{
FolderModel folderModel = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
@@ -246,7 +246,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that tagged folder can be tagged again")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void addTagToATaggedFolder() throws Exception
public void addTagToATaggedFolder()
{
FolderModel folderModel = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
@@ -269,11 +269,11 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Using collaborator provide more than one tag element")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void provideMoreThanOneTagElement() throws Exception
public void provideMoreThanOneTagElement()
{
FolderModel folderModel = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
String tagValue1 = RandomData.getRandomName("tag1");
String tagValue2 = RandomData.getRandomName("tag2");
String tagValue1 = RandomData.getRandomName("tag1").toLowerCase();
String tagValue2 = RandomData.getRandomName("tag2").toLowerCase();
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteCollaborator));
@@ -288,7 +288,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that manager cannot add tag with special characters.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void addTagWithSpecialCharacters() throws Exception
public void addTagWithSpecialCharacters()
{
FolderModel folderModel = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();
String specialCharsTag = "!@#$%^&*()'\".,<>-_+=|\\";
@@ -301,7 +301,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that you cannot tag a comment and it returns status code 405")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void addTagToAComment() throws Exception
public void addTagToAComment()
{
FileModel file = dataContent.usingSite(siteModel).usingUser(adminUserModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
String comment = "comment for a tag";
@@ -316,7 +316,7 @@ public class AddTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that you cannot tag a tag and it returns status code 405")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void addTagToATag() throws Exception
public void addTagToATag()
{
FileModel file = dataContent.usingSite(siteModel).usingUser(adminUserModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
@@ -326,4 +326,17 @@ public class AddTagTests extends TagsDataPrep
restClient.withCoreAPI().usingResource(file).addTag(tagValue);
restClient.assertStatusCodeIs(HttpStatus.METHOD_NOT_ALLOWED).assertLastError().containsSummary(RestErrorModel.CANNOT_TAG);
}
}
@TestRail (section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify the tag name is converted to lower case before being applied")
@Test (groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void tagNameIsConvertedToLowerCase()
{
restClient.authenticateUser(adminUserModel);
String tagName = RandomData.getRandomName("TaG-oNe");
returnedModel = restClient.withCoreAPI().usingResource(document).addTag(tagName);
restClient.assertStatusCodeIs(HttpStatus.CREATED);
returnedModel.assertThat().field("tag").is(tagName.toLowerCase())
.and().field("id").isNotEmpty();
}
}

View File

@@ -30,14 +30,14 @@ public class AddTagsTests extends TagsDataPrep
@BeforeMethod(alwaysRun = true)
public void generateRandomTagsList()
{
tag1 = RandomData.getRandomName("tag");
tag2 = RandomData.getRandomName("tag");
tag1 = RandomData.getRandomName("tag").toLowerCase();
tag2 = RandomData.getRandomName("tag").toLowerCase();
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify admin user adds multiple tags with Rest API and status code is 201")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsAbleToAddTags() throws Exception
public void adminIsAbleToAddTags()
{
restClient.authenticateUser(adminUserModel);
returnedCollection = restClient.withCoreAPI().usingResource(document).addTags(tag1, tag2);
@@ -49,7 +49,7 @@ public class AddTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY,
description = "Verify Manager user adds multiple tags with Rest API and status code is 201")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
public void managerIsAbleToAddTags() throws Exception
public void managerIsAbleToAddTags()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteManager));
returnedCollection = restClient.withCoreAPI().usingResource(document).addTags(tag1, tag2);
@@ -61,7 +61,7 @@ public class AddTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
executionType = ExecutionType.SANITY, description = "Verify Collaborator user adds multiple tags with Rest API and status code is 201")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void collaboratorIsAbleToAddTags() throws Exception
public void collaboratorIsAbleToAddTags()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteCollaborator));
returnedCollection = restClient.withCoreAPI().usingResource(document).addTags(tag1, tag2);
@@ -73,7 +73,7 @@ public class AddTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Contributor user doesn't have permission to add multiple tags with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void contributorIsNotAbleToAddTagsToAnotherContent() throws Exception
public void contributorIsNotAbleToAddTagsToAnotherContent()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteContributor));
restClient.withCoreAPI().usingResource(document).addTags(tag1, tag2);
@@ -83,7 +83,7 @@ public class AddTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Contributor user adds multiple tags to his content with Rest API and status code is 201")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void contributorIsAbleToAddTagsToHisContent() throws Exception
public void contributorIsAbleToAddTagsToHisContent()
{
userModel = usersWithRoles.getOneUserWithRole(UserRole.SiteContributor);
restClient.authenticateUser(userModel);
@@ -97,7 +97,7 @@ public class AddTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Consumer user doesn't have permission to add multiple tags with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void consumerIsNotAbleToAddTags() throws Exception
public void consumerIsNotAbleToAddTags()
{
restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteConsumer)).withCoreAPI().usingResource(document).addTags(tag1, tag2);
restClient.assertStatusCodeIs(HttpStatus.FORBIDDEN).assertLastError().containsSummary(RestErrorModel.PERMISSION_WAS_DENIED);
@@ -107,7 +107,7 @@ public class AddTagsTests extends TagsDataPrep
description = "Verify user gets status code 401 if authentication call fails")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
// @Bug(id="MNT-16904", description = "It fails only on environment with tenants")
public void userIsNotAbleToAddTagsIfAuthenticationFails() throws Exception
public void userIsNotAbleToAddTagsIfAuthenticationFails()
{
UserModel siteManager = usersWithRoles.getOneUserWithRole(UserRole.SiteManager);
String managerPassword = siteManager.getPassword();
@@ -120,7 +120,7 @@ public class AddTagsTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API,
TestGroup.TAGS }, executionType = ExecutionType.REGRESSION, description = "Verify include count parameter")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void getTagsUsingCountParam() throws Exception
public void getTagsUsingCountParam()
{
FileModel file = dataContent.usingSite(siteModel).usingUser(adminUserModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
String tagName = RandomData.getRandomName("tag");
@@ -139,4 +139,4 @@ public class AddTagsTests extends TagsDataPrep
}
}
}
}

View File

@@ -15,6 +15,7 @@ import org.alfresco.utility.testrail.ExecutionType;
import org.alfresco.utility.testrail.annotation.TestRail;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.http.HttpStatus;
import org.testng.annotations.Ignore;
import org.testng.annotations.Test;
/**
@@ -29,7 +30,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Admin user deletes tags with Rest API and status code is 204")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminIsAbleToDeleteTags() throws Exception
public void adminIsAbleToDeleteTags()
{
restClient.authenticateUser(adminUserModel);
tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -43,7 +44,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.SANITY,
description = "Verify Manager user deletes tags created by admin user with Rest API and status code is 204")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
public void managerIsAbleToDeleteTags() throws Exception
public void managerIsAbleToDeleteTags()
{
restClient.authenticateUser(adminUserModel);
tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -56,7 +57,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Collaborator user deletes tags created by admin user with Rest API and status code is 204")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void collaboratorIsAbleToDeleteTags() throws Exception
public void collaboratorIsAbleToDeleteTags()
{
restClient.authenticateUser(adminUserModel);
tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -69,7 +70,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Contributor user can't delete tags created by admin user with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void contributorIsNotAbleToDeleteTagsForAnotherUserContent() throws Exception
public void contributorIsNotAbleToDeleteTagsForAnotherUserContent()
{
restClient.authenticateUser(adminUserModel);
tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -82,7 +83,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Contributor user deletes tags created by him with Rest API and status code is 204")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void contributorIsAbleToDeleteTagsForHisContent() throws Exception
public void contributorIsAbleToDeleteTagsForHisContent()
{
userModel = usersWithRoles.getOneUserWithRole(UserRole.SiteContributor);
restClient.authenticateUser(userModel);
@@ -96,7 +97,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify Consumer user can't delete tags created by admin user with Rest API and status code is 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void consumerIsNotAbleToDeleteTags() throws Exception
public void consumerIsNotAbleToDeleteTags()
{
restClient.authenticateUser(adminUserModel);
tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -110,7 +111,7 @@ public class DeleteTagTests extends TagsDataPrep
description = "Verify user gets status code 401 if authentication call fails")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.SANITY })
// @Bug(id="MNT-16904", description = "It fails only on environment with tenants")
public void userIsNotAbleToDeleteTagIfAuthenticationFails() throws Exception
public void userIsNotAbleToDeleteTagIfAuthenticationFails()
{
restClient.authenticateUser(adminUserModel);
tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -127,7 +128,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that if user has no permission to remove tag returned status code is 403. Check default error model schema")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void deleteTagWithUserWithoutPermissionCheckDefaultErrorModelSchema() throws Exception
public void deleteTagWithUserWithoutPermissionCheckDefaultErrorModelSchema()
{
restClient.authenticateUser(adminUserModel);
RestTagModel tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -144,7 +145,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that if node does not exist returned status code is 404")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void deleteTagForANonexistentNode() throws Exception
public void deleteTagForANonexistentNode()
{
restClient.authenticateUser(adminUserModel);
RestTagModel tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -159,7 +160,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that if tag does not exist returned status code is 404")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void deleteTagThatDoesNotExist() throws Exception
public void deleteTagThatDoesNotExist()
{
restClient.authenticateUser(adminUserModel);
RestTagModel tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -172,7 +173,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that if tag id is empty returned status code is 405")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void deleteTagWithEmptyId() throws Exception
public void deleteTagWithEmptyId()
{
restClient.authenticateUser(adminUserModel);
RestTagModel tag = restClient.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -185,7 +186,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that folder tag can be deleted")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void deleteFolderTag() throws Exception
public void deleteFolderTag()
{
FolderModel folderModel = dataContent.usingUser(adminUserModel).usingSite(siteModel).createFolder();;
restClient.authenticateUser(adminUserModel);
@@ -200,7 +201,8 @@ public class DeleteTagTests extends TagsDataPrep
executionType = ExecutionType.REGRESSION, description = "Verify Manager user can't delete deleted tag.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
@Bug(id = "ACE-5455")
public void managerCannotDeleteDeletedTag() throws Exception
@Ignore
public void managerCannotDeleteDeletedTag()
{
tag = restClient.authenticateUser(adminUserModel)
.withCoreAPI().usingResource(document).addTag(RandomData.getRandomName("tag"));
@@ -216,7 +218,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
executionType = ExecutionType.REGRESSION, description = "Verify Collaborator user can delete long tag.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void userCollaboratorCanDeleteLongTag() throws Exception
public void userCollaboratorCanDeleteLongTag()
{
String longTag = RandomStringUtils.randomAlphanumeric(800);
@@ -231,7 +233,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
executionType = ExecutionType.REGRESSION, description = "Verify Manager user can delete short tag.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void managerCanDeleteShortTag() throws Exception
public void managerCanDeleteShortTag()
{
String shortTag = RandomStringUtils.randomAlphanumeric(10);
@@ -246,7 +248,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
executionType = ExecutionType.REGRESSION, description = "Verify Admin can delete tag then add it again.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void adminRemovesTagAndAddsItAgain() throws Exception
public void adminRemovesTagAndAddsItAgain()
{
String tagValue = RandomStringUtils.randomAlphanumeric(10);
@@ -267,7 +269,7 @@ public class DeleteTagTests extends TagsDataPrep
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS },
executionType = ExecutionType.REGRESSION, description = "Verify Manager user can delete tag added by another user.")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION })
public void managerCanDeleteTagAddedByAnotherUser() throws Exception
public void managerCanDeleteTagAddedByAnotherUser()
{
tag = restClient.authenticateUser(usersWithRoles.getOneUserWithRole(UserRole.SiteCollaborator))
.withCoreAPI().usingResource(document).addTag(RandomStringUtils.randomAlphanumeric(10));
@@ -276,4 +278,4 @@ public class DeleteTagTests extends TagsDataPrep
.withCoreAPI().usingResource(document).deleteTag(tag);
restClient.assertStatusCodeIs(HttpStatus.NO_CONTENT);
}
}
}

View File

@@ -150,18 +150,6 @@ public class GetNodeTagsTests extends TagsDataPrep
restClient.assertStatusCodeIs(HttpStatus.NOT_FOUND).assertLastError().containsSummary(String.format(RestErrorModel.ENTITY_NOT_FOUND, nodeRef));
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify that if node id is empty returns status code 403")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
public void emptyNodeIdTest() throws Exception
{
FileModel badDocument = dataContent.usingSite(siteModel).usingUser(adminUserModel).createContent(CMISUtil.DocumentType.TEXT_PLAIN);
badDocument.setNodeRef("");
restClient.authenticateUser(adminUserModel).withCoreAPI().usingResource(badDocument).getNodeTags();
restClient.assertStatusCodeIs(HttpStatus.NOT_FOUND).assertLastError().containsSummary(String.format(RestErrorModel.ENTITY_NOT_FOUND, ""));
}
@TestRail(section = { TestGroup.REST_API, TestGroup.TAGS }, executionType = ExecutionType.REGRESSION,
description = "Verify folder tags")
@Test(groups = { TestGroup.REST_API, TestGroup.TAGS, TestGroup.REGRESSION})
@@ -303,4 +291,4 @@ public class GetNodeTagsTests extends TagsDataPrep
.and().field("skipCount").is("10000");
returnedCollection.assertThat().entriesListCountIs(0);
}
}
}

View File

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

View File

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

19
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>20.114</version>
<version>20.140-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Alfresco Community Repo Parent</name>
@@ -52,8 +52,8 @@
<dependency.alfresco-messaging-repo.version>1.2.20</dependency.alfresco-messaging-repo.version>
<dependency.activiti-engine.version>5.23.0</dependency.activiti-engine.version>
<dependency.activiti.version>5.23.0</dependency.activiti.version>
<dependency.alfresco-transform-service.version>2.0.0</dependency.alfresco-transform-service.version>
<dependency.alfresco-transform-core.version>3.0.0</dependency.alfresco-transform-core.version>
<dependency.alfresco-transform-service.version>2.1.0-A5</dependency.alfresco-transform-service.version>
<dependency.alfresco-transform-core.version>3.1.0-A5</dependency.alfresco-transform-core.version>
<dependency.alfresco-greenmail.version>6.5</dependency.alfresco-greenmail.version>
<dependency.acs-event-model.version>0.0.18</dependency.acs-event-model.version>
@@ -62,7 +62,7 @@
<dependency.jackson.version>2.15.0-rc1</dependency.jackson.version>
<dependency.cxf.version>3.5.5</dependency.cxf.version>
<dependency.opencmis.version>1.0.0</dependency.opencmis.version>
<dependency.webscripts.version>8.38</dependency.webscripts.version>
<dependency.webscripts.version>8.40</dependency.webscripts.version>
<dependency.bouncycastle.version>1.70</dependency.bouncycastle.version>
<dependency.mockito-core.version>4.9.0</dependency.mockito-core.version>
<dependency.assertj.version>3.24.2</dependency.assertj.version>
@@ -83,7 +83,6 @@
<dependency.truezip.version>7.7.10</dependency.truezip.version>
<dependency.poi.version>5.2.2</dependency.poi.version>
<dependency.poi-ooxml-lite.version>5.2.3</dependency.poi-ooxml-lite.version>
<dependency.keycloak.version>18.0.0</dependency.keycloak.version>
<dependency.jboss.logging.version>3.5.0.Final</dependency.jboss.logging.version>
<dependency.camel.version>3.20.2</dependency.camel.version> <!-- when bumping this version, please keep track/sync with included netty.io dependencies -->
<dependency.netty.version>4.1.87.Final</dependency.netty.version> <!-- must be in sync with camels transitive dependencies, e.g.: netty-common -->
@@ -108,12 +107,12 @@
<dependency.jakarta-jws-api.version>2.1.0</dependency.jakarta-jws-api.version>
<dependency.jakarta-mail-api.version>1.6.5</dependency.jakarta-mail-api.version>
<dependency.jakarta-json-api.version>1.1.6</dependency.jakarta-json-api.version>
<dependency.jakarta-json-path.version>2.7.0</dependency.jakarta-json-path.version>
<dependency.json-smart.version>2.4.8</dependency.json-smart.version>
<dependency.jakarta-json-path.version>2.8.0</dependency.jakarta-json-path.version>
<dependency.json-smart.version>2.4.10</dependency.json-smart.version>
<dependency.jakarta-rpc-api.version>1.1.4</dependency.jakarta-rpc-api.version>
<alfresco.googledrive.version>3.4.0-M1</alfresco.googledrive.version>
<alfresco.aos-module.version>1.6.0-M1</alfresco.aos-module.version>
<alfresco.aos-module.version>1.6.0-A4</alfresco.aos-module.version>
<alfresco.api-explorer.version>7.3.0</alfresco.api-explorer.version> <!-- Also in alfresco-enterprise-share -->
<alfresco.maven-plugin.version>2.2.0</alfresco.maven-plugin.version>
@@ -123,7 +122,7 @@
<dependency.mysql.version>8.0.30</dependency.mysql.version>
<dependency.mysql-image.version>8</dependency.mysql-image.version>
<dependency.mariadb.version>2.7.4</dependency.mariadb.version>
<dependency.tas-utility.version>3.0.61</dependency.tas-utility.version>
<dependency.tas-utility.version>4.0.0</dependency.tas-utility.version>
<dependency.rest-assured.version>5.2.0</dependency.rest-assured.version>
<dependency.tas-email.version>1.11</dependency.tas-email.version>
<dependency.tas-webdav.version>1.7</dependency.tas-webdav.version>
@@ -151,7 +150,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>20.114</tag>
<tag>HEAD</tag>
</scm>
<distributionManagement>

View File

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

View File

@@ -39,6 +39,8 @@ import org.alfresco.rest.api.model.LockInfo;
import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.api.model.PathInfo;
import org.alfresco.rest.api.model.UserInfo;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
import org.alfresco.rest.framework.resource.content.BasicContentInfo;
import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
@@ -208,6 +210,19 @@ public interface Nodes
NodeRef validateNode(StoreRef storeRef, String nodeId);
NodeRef validateNode(String nodeId);
NodeRef validateNode(NodeRef nodeRef);
/**
* Check that the specified id refers to a valid node.
*
* @param nodeId The node id to look up using SpacesStore or an alias like -root-.
* @return The node ref.
* @throws InvalidArgumentException if the specified node id is not a valid format.
* @throws EntityNotFoundException if the specified node was not found in the database.
*/
default NodeRef validateOrLookupNode(String nodeId)
{
return validateOrLookupNode(nodeId, null);
}
NodeRef validateOrLookupNode(String nodeId, String path);
boolean nodeMatches(NodeRef nodeRef, Set<QName> expectedTypes, Set<QName> excludedTypes);

View File

@@ -37,7 +37,7 @@ import org.alfresco.service.cmr.repository.StoreRef;
public interface Tags
{
List<Tag> addTags(String nodeId, List<Tag> tags);
List<Tag> addTags(String nodeId, List<Tag> tags, Parameters parameters);
Tag getTag(StoreRef storeRef, String tagId);
void deleteTag(String nodeId, String tagId);
CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params);

View File

@@ -189,7 +189,7 @@ public class CategoriesImpl implements Categories
@Override
public List<Category> listCategoriesForNode(final String nodeId, final Parameters parameters)
{
final NodeRef contentNodeRef = nodes.validateOrLookupNode(nodeId, null);
final NodeRef contentNodeRef = nodes.validateOrLookupNode(nodeId);
verifyReadPermission(contentNodeRef);
verifyNodeType(contentNodeRef);
@@ -211,7 +211,7 @@ public class CategoriesImpl implements Categories
throw new InvalidArgumentException(NOT_A_VALID_CATEGORY);
}
final NodeRef contentNodeRef = nodes.validateOrLookupNode(nodeId, null);
final NodeRef contentNodeRef = nodes.validateOrLookupNode(nodeId);
verifyChangePermission(contentNodeRef);
verifyNodeType(contentNodeRef);
@@ -237,7 +237,7 @@ public class CategoriesImpl implements Categories
public void unlinkNodeFromCategory(final StoreRef storeRef, final String nodeId, final String categoryId, final Parameters parameters)
{
final NodeRef categoryNodeRef = getCategoryNodeRef(storeRef, categoryId);
final NodeRef contentNodeRef = nodes.validateOrLookupNode(nodeId, null);
final NodeRef contentNodeRef = nodes.validateOrLookupNode(nodeId);
verifyChangePermission(contentNodeRef);
verifyNodeType(contentNodeRef);

View File

@@ -1356,7 +1356,7 @@ public class NodesImpl implements Nodes
private void calculateRelativePath(String parentFolderNodeId, Node node)
{
NodeRef rootNodeRef = validateOrLookupNode(parentFolderNodeId, null);
NodeRef rootNodeRef = validateOrLookupNode(parentFolderNodeId);
try
{
// get the path elements
@@ -1741,7 +1741,7 @@ public class NodesImpl implements Nodes
@Override
public void deleteNode(String nodeId, Parameters parameters)
{
NodeRef nodeRef = validateOrLookupNode(nodeId, null);
NodeRef nodeRef = validateOrLookupNode(nodeId);
if (isSpecialNode(nodeRef, getNodeType(nodeRef)))
{
@@ -1785,7 +1785,7 @@ public class NodesImpl implements Nodes
validateProperties(nodeInfo.getProperties(), EXCLUDED_NS, Arrays.asList());
// check that requested parent node exists and it's type is a (sub-)type of folder
NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, null);
NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId);
// node name - mandatory
String nodeName = nodeInfo.getName();
@@ -2290,7 +2290,7 @@ public class NodesImpl implements Nodes
validateAspects(nodeInfo.getAspectNames(), EXCLUDED_NS, EXCLUDED_ASPECTS);
validateProperties(nodeInfo.getProperties(), EXCLUDED_NS, Arrays.asList());
final NodeRef nodeRef = validateOrLookupNode(nodeId, null);
final NodeRef nodeRef = validateOrLookupNode(nodeId);
QName nodeTypeQName = getNodeType(nodeRef);
@@ -2523,8 +2523,8 @@ public class NodesImpl implements Nodes
throw new InvalidArgumentException("Missing targetParentId");
}
final NodeRef parentNodeRef = validateOrLookupNode(targetParentId, null);
final NodeRef sourceNodeRef = validateOrLookupNode(sourceNodeId, null);
final NodeRef parentNodeRef = validateOrLookupNode(targetParentId);
final NodeRef sourceNodeRef = validateOrLookupNode(sourceNodeId);
FileInfo fi = moveOrCopyImpl(sourceNodeRef, parentNodeRef, name, isCopy);
return getFolderOrDocument(fi.getNodeRef().getId(), parameters);
@@ -2954,7 +2954,7 @@ public class NodesImpl implements Nodes
throw new InvalidArgumentException("The request content-type is not multipart: "+parentFolderNodeId);
}
NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId, null);
NodeRef parentNodeRef = validateOrLookupNode(parentFolderNodeId);
if (!nodeMatches(parentNodeRef, Collections.singleton(ContentModel.TYPE_FOLDER), null, false))
{
throw new InvalidArgumentException("NodeId of folder is expected: " + parentNodeRef.getId());
@@ -3385,7 +3385,7 @@ public class NodesImpl implements Nodes
@Override
public Node lock(String nodeId, LockInfo lockInfo, Parameters parameters)
{
NodeRef nodeRef = validateOrLookupNode(nodeId, null);
NodeRef nodeRef = validateOrLookupNode(nodeId);
if (isSpecialNode(nodeRef, getNodeType(nodeRef)))
{
@@ -3424,7 +3424,7 @@ public class NodesImpl implements Nodes
@Override
public Node unlock(String nodeId, Parameters parameters)
{
NodeRef nodeRef = validateOrLookupNode(nodeId, null);
NodeRef nodeRef = validateOrLookupNode(nodeId);
if (isSpecialNode(nodeRef, getNodeType(nodeRef)))
{

View File

@@ -185,7 +185,7 @@ public class QueriesImpl implements Queries, InitializingBean
String rootNodeId = parameters.getParameter(PARAM_ROOT_NODE_ID);
if (rootNodeId != null)
{
NodeRef nodeRef = nodes.validateOrLookupNode(rootNodeId, null);
NodeRef nodeRef = nodes.validateOrLookupNode(rootNodeId);
query.append("PATH:\"").append(getQNamePath(nodeRef.getId())).append("//*\" AND (");
}
if (term != null)

View File

@@ -695,7 +695,7 @@ public class RenditionsImpl implements Renditions, ResourceLoaderAware
{
if (versionLabelId != null)
{
nodeRef = nodes.validateOrLookupNode(nodeRef.getId(), null);
nodeRef = nodes.validateOrLookupNode(nodeRef.getId());
VersionHistory vh = versionService.getVersionHistory(nodeRef);
if (vh != null)
{

View File

@@ -30,6 +30,7 @@ import static java.util.stream.Collectors.toList;
import static org.alfresco.rest.antlr.WhereClauseParser.EQUALS;
import static org.alfresco.rest.antlr.WhereClauseParser.IN;
import static org.alfresco.rest.antlr.WhereClauseParser.MATCHES;
import static org.alfresco.service.cmr.tagging.TaggingService.TAG_ROOT_NODE_REF;
import java.util.ArrayList;
import java.util.Collection;
@@ -83,7 +84,6 @@ public class TagsImpl implements Tags
private static final String PARAM_WHERE_TAG = "tag";
static final String NOT_A_VALID_TAG = "An invalid parameter has been supplied";
static final String NO_PERMISSION_TO_MANAGE_A_TAG = "Current user does not have permission to manage a tag";
private final NodeRef tagParentNodeRef = new NodeRef("workspace://SpacesStore/tag:tag-root");
private Nodes nodes;
private NodeService nodeService;
@@ -96,7 +96,7 @@ public class TagsImpl implements Tags
this.typeConstraint = typeConstraint;
}
public void setNodes(Nodes nodes)
public void setNodes(Nodes nodes)
{
this.nodes = nodes;
}
@@ -115,9 +115,9 @@ public class TagsImpl implements Tags
this.authorityService = authorityService;
}
public List<Tag> addTags(String nodeId, final List<Tag> tags)
public List<Tag> addTags(String nodeId, final List<Tag> tags, final Parameters parameters)
{
NodeRef nodeRef = nodes.validateOrLookupNode(nodeId, null);
NodeRef nodeRef = nodes.validateOrLookupNode(nodeId);
if (!typeConstraint.matches(nodeRef))
{
throw new UnsupportedResourceOperationException("Cannot tag this node");
@@ -128,9 +128,15 @@ public class TagsImpl implements Tags
{
List<Pair<String, NodeRef>> tagNodeRefs = taggingService.addTags(nodeRef, tagValues);
List<Tag> ret = new ArrayList<>(tags.size());
List<Pair<String, Integer>> tagsCountPairList = taggingService.findTaggedNodesAndCountByTagName(nodeRef.getStoreRef());
Map<String, Integer> tagsCountMap = tagsCountPairList.stream().collect(Collectors.toMap(Pair::getFirst,Pair::getSecond));
for (Pair<String, NodeRef> pair : tagNodeRefs)
{
ret.add(new Tag(pair.getSecond(), pair.getFirst()));
Tag createdTag = new Tag(pair.getSecond(), pair.getFirst());
if (parameters.getInclude().contains(PARAM_INCLUDE_COUNT)) {
createdTag.setCount(Optional.ofNullable(tagsCountMap.get(createdTag.getTag())).orElse(0) + 1);
}
ret.add(createdTag);
}
return ret;
}
@@ -158,21 +164,25 @@ public class TagsImpl implements Tags
taggingService.deleteTag(storeRef, tagValue);
}
@Override
@Override
public CollectionWithPagingInfo<Tag> getTags(StoreRef storeRef, Parameters params)
{
Paging paging = params.getPaging();
Map<Integer, Collection<String>> namesFilters = resolveTagNamesQuery(params.getQuery());
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging), namesFilters.get(EQUALS), namesFilters.get(MATCHES));
Paging paging = params.getPaging();
Map<Integer, Collection<String>> namesFilters = resolveTagNamesQuery(params.getQuery());
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(storeRef, Util.getPagingRequest(paging), namesFilters.get(EQUALS), namesFilters.get(MATCHES));
Integer totalItems = results.getTotalResultCount().getFirst();
List<Pair<NodeRef, String>> page = results.getPage();
List<Tag> tags = new ArrayList<>(page.size());
List<Pair<String, Integer>> tagsByCount;
Map<String, Integer> tagsByCountMap = new HashMap<>();
for (Pair<NodeRef, String> pair : page)
{
Tag selectedTag = new Tag(pair.getFirst(), pair.getSecond());
tags.add(selectedTag);
}
if (params.getInclude().contains(PARAM_INCLUDE_COUNT))
{
tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef);
List<Pair<String, Integer>> tagsByCount = taggingService.findTaggedNodesAndCountByTagName(storeRef);
Map<String, Integer> tagsByCountMap = new HashMap<>();
if (tagsByCount != null)
{
for (Pair<String, Integer> tagByCountElem : tagsByCount)
@@ -180,12 +190,7 @@ public class TagsImpl implements Tags
tagsByCountMap.put(tagByCountElem.getFirst(), tagByCountElem.getSecond());
}
}
}
for (Pair<NodeRef, String> pair : page)
{
Tag selectedTag = new Tag(pair.getFirst(), pair.getSecond());
selectedTag.setCount(Optional.ofNullable(tagsByCountMap.get(selectedTag.getTag())).orElse(0));
tags.add(selectedTag);
tags.forEach(tag -> tag.setCount(Optional.ofNullable(tagsByCountMap.get(tag.getTag())).orElse(0)));
}
return CollectionWithPagingInfo.asPaged(paging, tags, results.hasMoreItems(), totalItems);
@@ -241,7 +246,7 @@ public class TagsImpl implements Tags
public CollectionWithPagingInfo<Tag> getTags(String nodeId, Parameters params)
{
NodeRef nodeRef = nodes.validateOrLookupNode(nodeId, null);
NodeRef nodeRef = nodes.validateOrLookupNode(nodeId);
PagingResults<Pair<NodeRef, String>> results = taggingService.getTags(nodeRef, Util.getPagingRequest(params.getPaging()));
Integer totalItems = results.getTotalResultCount().getFirst();
List<Pair<NodeRef, String>> page = results.getPage();
@@ -324,7 +329,7 @@ public class TagsImpl implements Tags
private NodeRef checkTagRootAsNodePrimaryParent(String tagId, NodeRef tagNodeRef)
{
if ( tagNodeRef == null || !nodeService.getPrimaryParent(tagNodeRef).getParentRef().equals(tagParentNodeRef))
if ( tagNodeRef == null || !nodeService.getPrimaryParent(tagNodeRef).getParentRef().equals(TAG_ROOT_NODE_REF))
{
throw new EntityNotFoundException(tagId);
}

View File

@@ -134,7 +134,7 @@ public class RestRuleModelMapper implements RestModelMapper<Rule, org.alfresco.s
public org.alfresco.service.cmr.rule.Rule toServiceModel(Rule restRuleModel)
{
final org.alfresco.service.cmr.rule.Rule serviceRule = new org.alfresco.service.cmr.rule.Rule();
final NodeRef nodeRef = (restRuleModel.getId() != null) ? nodes.validateOrLookupNode(restRuleModel.getId(), null) : null;
final NodeRef nodeRef = (restRuleModel.getId() != null) ? nodes.validateOrLookupNode(restRuleModel.getId()) : null;
serviceRule.setNodeRef(nodeRef);
serviceRule.setTitle(restRuleModel.getName());
serviceRule.setDescription(restRuleModel.getDescription());

View File

@@ -129,7 +129,7 @@ public class RestRuleSimpleConditionModelMapper implements RestModelMapper<Simpl
parameterValues.put(InCategoryEvaluator.PARAM_CATEGORY_ASPECT, ContentModel.ASPECT_GEN_CLASSIFIABLE);
try
{
parameterValues.put(InCategoryEvaluator.PARAM_CATEGORY_VALUE, nodes.validateOrLookupNode(parameter, null));
parameterValues.put(InCategoryEvaluator.PARAM_CATEGORY_VALUE, nodes.validateOrLookupNode(parameter));
} catch (EntityNotFoundException e) {
throw new InvalidArgumentException(CATEGORY_INVALID_MSG);
}

View File

@@ -173,7 +173,7 @@ public class ActionParameterConverter
}
else if (typeQName.isMatch(DataTypeDefinition.NODE_REF))
{
NodeRef nodeRef = nodes.validateOrLookupNode(stringValue, null);
NodeRef nodeRef = nodes.validateOrLookupNode(stringValue);
if (permissionService.hasReadPermission(nodeRef) != ALLOWED)
{
throw new EntityNotFoundException(stringValue);

View File

@@ -72,7 +72,7 @@ public class NodeValidator
{
try
{
final NodeRef nodeRef = nodes.validateOrLookupNode(folderNodeId, null);
final NodeRef nodeRef = nodes.validateOrLookupNode(folderNodeId);
validatePermission(requireChangePermission, nodeRef);
verifyNodeType(nodeRef, ContentModel.TYPE_FOLDER, null);

View File

@@ -26,6 +26,7 @@
package org.alfresco.rest.api.model;
import java.util.Objects;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.alfresco.rest.framework.resource.UniqueId;
@@ -50,7 +51,7 @@ public class Tag implements Comparable<Tag>
public Tag(NodeRef nodeRef, String tag)
{
this.nodeRef = nodeRef;
this.tag = tag;
setTag(tag);
}
@JsonProperty("id")
@@ -72,7 +73,7 @@ public class Tag implements Comparable<Tag>
public void setTag(String tag)
{
this.tag = tag;
this.tag = Optional.ofNullable(tag).map(String::toLowerCase).orElse(null);
}
public Integer getCount()

View File

@@ -31,13 +31,9 @@ import org.alfresco.rest.framework.resource.RelationshipResource;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
import org.alfresco.rest.framework.resource.parameters.SortColumn;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.util.ParameterCheck;
import java.util.List;
import java.util.stream.Collectors;
@RelationshipResource(name = "action-definitions", entityResource = NodesEntityResource.class, title = "Node action definitions")
public class NodeActionDefinitionsRelation extends AbstractNodeRelation
implements RelationshipResourceAction.Read<ActionDefinition>
@@ -59,7 +55,7 @@ public class NodeActionDefinitionsRelation extends AbstractNodeRelation
@Override
public CollectionWithPagingInfo<ActionDefinition> readAll(String entityResourceId, Parameters params)
{
NodeRef parentNodeRef = nodes.validateOrLookupNode(entityResourceId, null);
NodeRef parentNodeRef = nodes.validateOrLookupNode(entityResourceId);
return actions.getActionDefinitions(parentNodeRef, params);
}
}

View File

@@ -25,6 +25,11 @@
*/
package org.alfresco.rest.api.nodes;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.alfresco.rest.antlr.WhereClauseParser;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.Node;
@@ -41,11 +46,6 @@ import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QNamePattern;
import org.alfresco.service.namespace.RegexQNamePattern;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Node Parents
*
@@ -67,7 +67,7 @@ public class NodeParentsRelation extends AbstractNodeRelation implements Relatio
@WebApiDescription(title = "Return a list of parent nodes based on child assocs")
public CollectionWithPagingInfo<Node> readAll(String childNodeId, Parameters parameters)
{
NodeRef childNodeRef = nodes.validateOrLookupNode(childNodeId, null);
NodeRef childNodeRef = nodes.validateOrLookupNode(childNodeId);
QNamePattern assocTypeQNameParam = RegexQNamePattern.MATCH_ALL;

View File

@@ -25,6 +25,8 @@
*/
package org.alfresco.rest.api.nodes;
import java.util.List;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.AssocChild;
import org.alfresco.rest.api.model.Node;
@@ -41,8 +43,6 @@ import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.QNamePattern;
import org.alfresco.service.namespace.RegexQNamePattern;
import java.util.List;
/**
* Node Secondary Children
*
@@ -71,7 +71,7 @@ public class NodeSecondaryChildrenRelation extends AbstractNodeRelation implemen
@WebApiDescription(title = "Return a paged list of secondary child nodes based on child assocs")
public CollectionWithPagingInfo<Node> readAll(String parentNodeId, Parameters parameters)
{
NodeRef parentNodeRef = nodes.validateOrLookupNode(parentNodeId, null);
NodeRef parentNodeRef = nodes.validateOrLookupNode(parentNodeId);
QNamePattern assocTypeQNameParam = getAssocTypeFromWhereElseAll(parameters);

View File

@@ -25,6 +25,8 @@
*/
package org.alfresco.rest.api.nodes;
import java.util.List;
import org.alfresco.rest.api.model.Node;
import org.alfresco.rest.framework.WebApiDescription;
import org.alfresco.rest.framework.resource.RelationshipResource;
@@ -35,8 +37,6 @@ import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.QNamePattern;
import java.util.List;
/**
* Node Sources - list node (peer) associations from target to sources
*
@@ -54,7 +54,7 @@ public class NodeSourcesRelation extends AbstractNodeRelation implements Relatio
@WebApiDescription(title = "Return a paged list of sources nodes based on (peer) assocs")
public CollectionWithPagingInfo<Node> readAll(String targetNodeId, Parameters parameters)
{
NodeRef targetNodeRef = nodes.validateOrLookupNode(targetNodeId, null);
NodeRef targetNodeRef = nodes.validateOrLookupNode(targetNodeId);
QNamePattern assocTypeQNameParam = getAssocTypeFromWhereElseAll(parameters);

View File

@@ -1,28 +1,28 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.rest.api.nodes;
import java.util.List;
@@ -61,7 +61,7 @@ public class NodeTagsRelation implements RelationshipResourceAction.Create<Tag>,
@WebApiDescription(title="Adds one or more tags to the node with id 'nodeId'.")
public List<Tag> create(String nodeId, List<Tag> tagsToCreate, Parameters parameters)
{
return tags.addTags(nodeId, tagsToCreate);
return tags.addTags(nodeId, tagsToCreate, parameters);
}
@Override

View File

@@ -25,6 +25,8 @@
*/
package org.alfresco.rest.api.nodes;
import java.util.List;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.model.AssocTarget;
import org.alfresco.rest.api.model.Node;
@@ -40,8 +42,6 @@ import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.namespace.QNamePattern;
import org.alfresco.service.namespace.RegexQNamePattern;
import java.util.List;
/**
* Node Targets
*
@@ -64,7 +64,7 @@ public class NodeTargetsRelation extends AbstractNodeRelation implements
@WebApiDescription(title = "Return a paged list of target nodes based on (peer) assocs")
public CollectionWithPagingInfo<Node> readAll(String sourceNodeId, Parameters parameters)
{
NodeRef sourceNodeRef = nodes.validateOrLookupNode(sourceNodeId, null);
NodeRef sourceNodeRef = nodes.validateOrLookupNode(sourceNodeId);
QNamePattern assocTypeQNameParam = getAssocTypeFromWhereElseAll(parameters);

View File

@@ -25,6 +25,13 @@
*/
package org.alfresco.rest.api.nodes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.directurl.DirectAccessUrlDisabledException;
import org.alfresco.repo.node.integrity.IntegrityException;
@@ -65,13 +72,6 @@ import org.alfresco.util.ParameterCheck;
import org.alfresco.util.PropertyCheck;
import org.springframework.beans.factory.InitializingBean;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Node Versions - version history
*
@@ -117,7 +117,7 @@ public class NodeVersionsRelation extends AbstractNodeRelation implements
@WebApiDescription(title = "Return version history as a paged list of version node infos")
public CollectionWithPagingInfo<Node> readAll(String nodeId, Parameters parameters)
{
NodeRef nodeRef = nodes.validateOrLookupNode(nodeId, null);
NodeRef nodeRef = nodes.validateOrLookupNode(nodeId);
VersionHistory vh = versionService.getVersionHistory(nodeRef);
@@ -293,7 +293,7 @@ public class NodeVersionsRelation extends AbstractNodeRelation implements
public Version findVersion(String nodeId, String versionLabelId)
{
NodeRef nodeRef = nodes.validateOrLookupNode(nodeId, null);
NodeRef nodeRef = nodes.validateOrLookupNode(nodeId);
VersionHistory vh = versionService.getVersionHistory(nodeRef);
if (vh != null)
{

View File

@@ -128,7 +128,7 @@ public class CategoriesImplTest
{
given(authorityServiceMock.hasAdminAuthority()).willReturn(true);
given(nodesMock.validateNode(CATEGORY_ID)).willReturn(CATEGORY_NODE_REF);
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID, null)).willReturn(CONTENT_NODE_REF);
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID)).willReturn(CONTENT_NODE_REF);
given(nodesMock.isSubClass(any(), any(), anyBoolean())).willReturn(true);
given(typeConstraint.matches(any())).willReturn(true);
given(permissionServiceMock.hasReadPermission(any())).willReturn(AccessStatus.ALLOWED);
@@ -900,7 +900,7 @@ public class CategoriesImplTest
// when
final List<Category> actualLinkedCategories = objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, categoryLinks, parametersMock);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID);
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(typeConstraint).should().matches(CONTENT_NODE_REF);
@@ -1011,12 +1011,12 @@ public class CategoriesImplTest
@Test
public void testLinkNodeToCategories_withInvalidNodeId()
{
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID, null)).willThrow(EntityNotFoundException.class);
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID)).willThrow(EntityNotFoundException.class);
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock));
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID);
then(permissionServiceMock).shouldHaveNoInteractions();
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)
@@ -1031,7 +1031,7 @@ public class CategoriesImplTest
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.linkNodeToCategories(CONTENT_NODE_ID, List.of(CATEGORY), parametersMock));
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID);
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)
@@ -1118,7 +1118,7 @@ public class CategoriesImplTest
objectUnderTest.unlinkNodeFromCategory(CONTENT_NODE_ID, CATEGORY_ID, parametersMock);
then(nodesMock).should().validateNode(CATEGORY_ID);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID);
then(permissionServiceMock).should().hasPermission(CONTENT_NODE_REF, PermissionService.CHANGE_PERMISSIONS);
then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(typeConstraint).should().matches(CONTENT_NODE_REF);
@@ -1155,7 +1155,7 @@ public class CategoriesImplTest
// when
final List<Category> actualCategories = objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID);
then(permissionServiceMock).should().hasReadPermission(CONTENT_NODE_REF);
then(permissionServiceMock).shouldHaveNoMoreInteractions();
then(typeConstraint).should().matches(CONTENT_NODE_REF);
@@ -1176,12 +1176,12 @@ public class CategoriesImplTest
@Test
public void testListCategoriesForNode_withInvalidNodeId()
{
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID, null)).willThrow(EntityNotFoundException.class);
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID)).willThrow(EntityNotFoundException.class);
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock));
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID);
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)
.isInstanceOf(EntityNotFoundException.class);
@@ -1195,7 +1195,7 @@ public class CategoriesImplTest
// when
final Throwable actualException = catchThrowable(() -> objectUnderTest.listCategoriesForNode(CONTENT_NODE_ID, parametersMock));
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(CONTENT_NODE_ID);
then(permissionServiceMock).should().hasReadPermission(CONTENT_NODE_REF);
then(nodeServiceMock).shouldHaveNoInteractions();
assertThat(actualException)

View File

@@ -129,7 +129,7 @@ public class TagsImplTest
then(taggingServiceMock).should().getTags(eq(STORE_REF_WORKSPACE_SPACESSTORE), any(PagingRequest.class), isNull(), isNull());
then(taggingServiceMock).shouldHaveNoMoreInteractions();
final List<Tag> expectedTags = createTagsWithNodeRefs(List.of(TAG_NAME)).stream().peek(tag -> tag.setCount(0)).collect(toList());
final List<Tag> expectedTags = createTagsWithNodeRefs(List.of(TAG_NAME));
assertEquals(expectedTags, actualTags.getCollection());
}
@@ -151,6 +151,30 @@ public class TagsImplTest
assertEquals(expectedTags, actualTags.getCollection());
}
/** Check that we can get counts for two tags - one in use and one not applied to any nodes. */
@Test
public void testGetTags_verifyCountPopulatedCorrectly()
{
NodeRef tagNodeA = new NodeRef("tag://A/");
NodeRef tagNodeB = new NodeRef("tag://B/");
List<Pair<NodeRef, String>> tagPairs = List.of(new Pair<>(tagNodeA, "taga"), new Pair<>(tagNodeB, "tagb"));
given(parametersMock.getPaging()).willReturn(pagingMock);
given(taggingServiceMock.getTags(any(StoreRef.class), any(PagingRequest.class), any(), any())).willReturn(pagingResultsMock);
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(Integer.MAX_VALUE, 0));
given(pagingResultsMock.getPage()).willReturn(tagPairs);
given(parametersMock.getInclude()).willReturn(List.of("count"));
// Only taga is included in the returned list since tagb is not in use.
given(taggingServiceMock.findTaggedNodesAndCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE)).willReturn(List.of(new Pair<>("taga", 5)));
final CollectionWithPagingInfo<Tag> actualTags = objectUnderTest.getTags(STORE_REF_WORKSPACE_SPACESSTORE, parametersMock);
then(taggingServiceMock).should().findTaggedNodesAndCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE);
final List<Tag> expectedTags = List.of(Tag.builder().tag("taga").nodeRef(tagNodeA).count(5).create(),
Tag.builder().tag("tagb").nodeRef(tagNodeB).count(0).create());
assertEquals(expectedTags, actualTags.getCollection());
}
@Test
public void testGetTags_withEqualsClauseWhereQuery()
{
@@ -422,45 +446,50 @@ public class TagsImplTest
@Test
public void testAddTags()
{
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID, null)).willReturn(CONTENT_NODE_REF);
NodeRef tagNodeA = new NodeRef("tag://A/");
NodeRef tagNodeB = new NodeRef("tag://B/");
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID)).willReturn(CONTENT_NODE_REF);
given(typeConstraintMock.matches(CONTENT_NODE_REF)).willReturn(true);
List<Pair<String, NodeRef>> pairs = List.of(new Pair<>("tagA", new NodeRef("tag://A/")), new Pair<>("tagB", new NodeRef("tag://B/")));
List<Pair<String, NodeRef>> pairs = List.of(new Pair<>("taga", new NodeRef("tag://A/")), new Pair<>("tagb", new NodeRef("tag://B/")));
List<String> tagNames = pairs.stream().map(Pair::getFirst).collect(toList());
List<Tag> tags = tagNames.stream().map(name -> Tag.builder().tag(name).create()).collect(toList());
given(taggingServiceMock.addTags(CONTENT_NODE_REF, tagNames)).willReturn(pairs);
given(taggingServiceMock.findTaggedNodesAndCountByTagName(STORE_REF_WORKSPACE_SPACESSTORE)).willReturn(List.of(new Pair<>("taga", 4)));
given(parametersMock.getInclude()).willReturn(List.of("count"));
List<Tag> actual = objectUnderTest.addTags(CONTENT_NODE_ID, tags);
List<Tag> actual = objectUnderTest.addTags(CONTENT_NODE_ID, tags, parametersMock);
List<Tag> expected = pairs.stream().map(pair -> new Tag(pair.getSecond(), pair.getFirst())).collect(toList());
final List<Tag> expected = List.of(Tag.builder().tag("taga").nodeRef(tagNodeA).count(5).create(),
Tag.builder().tag("tagb").nodeRef(tagNodeB).count(1).create());
assertEquals("Unexpected tags returned.", expected, actual);
}
@Test(expected = InvalidArgumentException.class)
public void testAddTagsToInvalidNode()
{
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID, null)).willThrow(new InvalidArgumentException());
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID)).willThrow(new InvalidArgumentException());
List<Tag> tags = List.of(Tag.builder().tag("tag1").create());
objectUnderTest.addTags(CONTENT_NODE_ID, tags);
objectUnderTest.addTags(CONTENT_NODE_ID, tags, parametersMock);
}
@Test(expected = UnsupportedResourceOperationException.class)
public void testAddTagsToWrongTypeOfNode()
{
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID, null)).willReturn(CONTENT_NODE_REF);
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID)).willReturn(CONTENT_NODE_REF);
given(typeConstraintMock.matches(CONTENT_NODE_REF)).willReturn(false);
List<Tag> tags = List.of(Tag.builder().tag("tag1").create());
objectUnderTest.addTags(CONTENT_NODE_ID, tags);
objectUnderTest.addTags(CONTENT_NODE_ID, tags, parametersMock);
}
@Test
public void testGetTagsForNode()
{
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID, null)).willReturn(CONTENT_NODE_REF);
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID)).willReturn(CONTENT_NODE_REF);
given(parametersMock.getPaging()).willReturn(pagingMock);
List<Pair<NodeRef, String>> pairs = List.of(new Pair<>(new NodeRef("tag://A/"), "tagA"), new Pair<>(new NodeRef("tag://B/"), "tagB"));
List<Pair<NodeRef, String>> pairs = List.of(new Pair<>(new NodeRef("tag://A/"), "taga"), new Pair<>(new NodeRef("tag://B/"), "tagb"));
given(taggingServiceMock.getTags(eq(CONTENT_NODE_REF), any(PagingRequest.class))).willReturn(pagingResultsMock);
given(pagingResultsMock.getTotalResultCount()).willReturn(new Pair<>(null, null));
given(pagingResultsMock.getPage()).willReturn(pairs);
@@ -474,7 +503,7 @@ public class TagsImplTest
@Test (expected = InvalidArgumentException.class)
public void testGetTagsFromInvalidNode()
{
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID, null)).willThrow(new InvalidArgumentException());
given(nodesMock.validateOrLookupNode(CONTENT_NODE_ID)).willThrow(new InvalidArgumentException());
objectUnderTest.getTags(CONTENT_NODE_ID, parametersMock);
}

View File

@@ -134,7 +134,7 @@ public class RestRuleModelMapperTest
// when
final org.alfresco.service.cmr.rule.Rule actualRuleModel = objectUnderTest.toServiceModel(rule);
then(nodesMock).should().validateOrLookupNode(RULE_ID, null);
then(nodesMock).should().validateOrLookupNode(RULE_ID);
then(nodesMock).shouldHaveNoMoreInteractions();
then(actionMapperMock).should().toServiceModel(List.of(action));
then(actionMapperMock).shouldHaveNoMoreInteractions();

View File

@@ -275,7 +275,7 @@ public class RestRuleSimpleConditionModelMapperTest
{
final SimpleCondition simpleCondition = createSimpleCondition(PARAM_CATEGORY);
final NodeRef defaultNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, PARAMETER_DEFAULT);
given(nodesMock.validateOrLookupNode(PARAMETER_DEFAULT, null)).willReturn(defaultNodeRef);
given(nodesMock.validateOrLookupNode(PARAMETER_DEFAULT)).willReturn(defaultNodeRef);
// when
final ActionCondition actualActionCondition = objectUnderTest.toServiceModel(simpleCondition);

View File

@@ -44,7 +44,6 @@ import java.io.Serializable;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.alfresco.repo.action.executer.AddFeaturesActionExecuter;
import org.alfresco.repo.action.executer.CheckInActionExecuter;
import org.alfresco.repo.action.executer.CheckOutActionExecuter;
@@ -129,8 +128,8 @@ public class ActionParameterConverterTest
@Before
public void setUp()
{
given(nodes.validateOrLookupNode(DUMMY_FOLDER_NODE_ID, null)).willReturn(DUMMY_FOLDER_NODE);
given(nodes.validateOrLookupNode(DUMMY_SCRIPT_NODE_ID, null)).willReturn(DUMMY_SCRIPT_NODE);
given(nodes.validateOrLookupNode(DUMMY_FOLDER_NODE_ID)).willReturn(DUMMY_FOLDER_NODE);
given(nodes.validateOrLookupNode(DUMMY_SCRIPT_NODE_ID)).willReturn(DUMMY_SCRIPT_NODE);
given(permissionService.hasReadPermission(DUMMY_FOLDER_NODE)).willReturn(ALLOWED);
given(permissionService.hasReadPermission(DUMMY_SCRIPT_NODE)).willReturn(ALLOWED);
}
@@ -598,7 +597,7 @@ public class ActionParameterConverterTest
String permissionDeniedNodeId = "permission://denied/node";
final Map<String, Serializable> params = Map.of(PARAM_DESTINATION_FOLDER, permissionDeniedNodeId);
NodeRef permissionDeniedNode = new NodeRef(permissionDeniedNodeId);
given(nodes.validateOrLookupNode(permissionDeniedNodeId, null)).willReturn(permissionDeniedNode);
given(nodes.validateOrLookupNode(permissionDeniedNodeId)).willReturn(permissionDeniedNode);
given(permissionService.hasReadPermission(permissionDeniedNode)).willReturn(DENIED);
given(actionService.getActionDefinition(name)).willReturn(actionDefinition);

View File

@@ -101,7 +101,7 @@ public class NodeValidatorTest
public void setUp() throws Exception
{
MockitoAnnotations.openMocks(this);
given(nodesMock.validateOrLookupNode(eq(FOLDER_NODE_ID), any())).willReturn(folderNodeRef);
given(nodesMock.validateOrLookupNode(FOLDER_NODE_ID)).willReturn(folderNodeRef);
given(nodesMock.validateNode(RULE_SET_ID)).willReturn(ruleSetNodeRef);
given(nodesMock.validateNode(RULE_ID)).willReturn(ruleNodeRef);
given(nodesMock.nodeMatches(any(), any(), any())).willReturn(true);
@@ -115,7 +115,7 @@ public class NodeValidatorTest
// when
final NodeRef nodeRef = nodeValidator.validateFolderNode(FOLDER_NODE_ID, false);
then(nodesMock).should().validateOrLookupNode(FOLDER_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(FOLDER_NODE_ID);
then(nodesMock).should().nodeMatches(folderNodeRef, Set.of(TYPE_FOLDER), null);
then(nodesMock).shouldHaveNoMoreInteractions();
then(permissionServiceMock).should().hasReadPermission(folderNodeRef);
@@ -128,13 +128,13 @@ public class NodeValidatorTest
@Test
public void testValidateFolderNode_notExistingFolder()
{
given(nodesMock.validateOrLookupNode(any(), any())).willThrow(new EntityNotFoundException(FOLDER_NODE_ID));
given(nodesMock.validateOrLookupNode(FOLDER_NODE_ID)).willThrow(new EntityNotFoundException(FOLDER_NODE_ID));
//when
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(
() -> nodeValidator.validateFolderNode(FOLDER_NODE_ID, false));
then(nodesMock).should().validateOrLookupNode(FOLDER_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(FOLDER_NODE_ID);
then(nodesMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).shouldHaveNoInteractions();
}
@@ -148,7 +148,7 @@ public class NodeValidatorTest
assertThatExceptionOfType(InvalidArgumentException.class).isThrownBy(
() -> nodeValidator.validateFolderNode(FOLDER_NODE_ID, false));
then(nodesMock).should().validateOrLookupNode(FOLDER_NODE_ID, null);
then(nodesMock).should().validateOrLookupNode(FOLDER_NODE_ID);
then(nodesMock).should().nodeMatches(folderNodeRef, Set.of(TYPE_FOLDER), null);
then(nodesMock).shouldHaveNoMoreInteractions();
then(ruleServiceMock).shouldHaveNoInteractions();
@@ -431,11 +431,12 @@ public class NodeValidatorTest
then(ruleServiceMock).shouldHaveNoMoreInteractions();
}
private void resetNodesMock() {
private void resetNodesMock()
{
reset(nodesMock);
given(nodesMock.validateOrLookupNode(eq(FOLDER_NODE_ID), any())).willReturn(folderNodeRef);
given(nodesMock.validateOrLookupNode(FOLDER_NODE_ID)).willReturn(folderNodeRef);
given(nodesMock.validateNode(RULE_SET_ID)).willReturn(ruleSetNodeRef);
given(nodesMock.validateNode(RULE_ID)).willReturn(ruleNodeRef);
given(nodesMock.nodeMatches(ruleSetNodeRef, Set.of(ContentModel.TYPE_SYSTEM_FOLDER), null)).willReturn(true);
}
}
}

View File

@@ -37,10 +37,8 @@ import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.notNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.notNull;
import static org.mockito.Mockito.when;
import java.io.Serializable;
@@ -186,7 +184,7 @@ public class ResultMapperTests
when(sr.getVersionService()).thenReturn(versionService);
when(sr.getNodeService()).thenReturn(nodeService);
when(nodes.validateOrLookupNode(nullable(String.class), nullable(String.class))).thenAnswer(invocation ->
when(nodes.validateOrLookupNode(nullable(String.class))).thenAnswer(invocation ->
{
Object[] args = invocation.getArguments();
String aNode = (String)args[0];

View File

@@ -734,195 +734,6 @@ public class SharedLinkApiTest extends AbstractBaseApiTest
}
}
/**
* Tests shared links to file (content) in a multi-tenant system.
*
* <p>POST:</p>
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links}
*
* <p>DELETE:</p>
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>}
*
* <p>GET:</p>
* The following do not require authentication
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/content}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/renditions}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/renditions/<renditionId>}
* {@literal <host>:<port>/alfresco/api/<networkId>/public/alfresco/versions/1/shared-links/<sharedId>/renditions/<renditionId>/content}
*
*/
// TODO now covered by testSharedLinkCreateGetDelete ? (since base class now uses tenant context by default)
@Test
public void testSharedLinkCreateGetDelete_MultiTenant() throws Exception
{
// As user1
setRequestContext(user1);
String docLibNodeId = getSiteContainerNodeId(tSiteId, "documentLibrary");
String folderName = "folder" + System.currentTimeMillis() + "_1";
String folderId = createFolder(docLibNodeId, folderName, null).getId();
// create doc d1 - pdf
String fileName1 = "quick" + RUNID + "_1.pdf";
File file1 = getResourceFile("quick.pdf");
byte[] file1_originalBytes = Files.readAllBytes(Paths.get(file1.getAbsolutePath()));
String file1_MimeType = MimetypeMap.MIMETYPE_PDF;
MultiPartBuilder.MultiPartRequest reqBody = MultiPartBuilder.create()
.setFileData(new MultiPartBuilder.FileData(fileName1, file1, file1_MimeType))
.build();
HttpResponse response = post(getNodeChildrenUrl(folderId), reqBody.getBody(), null, reqBody.getContentType(), 201);
Document doc1 = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
String d1Id = doc1.getId();
assertNotNull(d1Id);
// create shared link to document 1
Map<String, String> body = new HashMap<>();
body.put("nodeId", d1Id);
response = post(URL_SHARED_LINKS, toJsonAsStringNonNull(body), 201);
QuickShareLink resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class);
String shared1Id = resp.getId();
assertNotNull(shared1Id);
assertEquals(d1Id, resp.getNodeId());
assertEquals(fileName1, resp.getName());
assertEquals(file1_MimeType, resp.getContent().getMimeType());
assertEquals(user1, resp.getSharedByUser().getId());
// allowable operations not included - no params
response = getSingle(QuickShareLinkEntityResource.class, shared1Id, null, 200);
resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class);
assertNull(resp.getAllowableOperations());
setRequestContext(null);
// unauth access to get shared link info
Map<String, String> params = Collections.singletonMap("include", "allowableOperations"); // note: this will be ignore for unauth access
response = getSingle(QuickShareLinkEntityResource.class, shared1Id, params, 200);
resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class);
assertEquals(shared1Id, resp.getId());
assertEquals(fileName1, resp.getName());
assertEquals(d1Id, resp.getNodeId());
assertNull(resp.getAllowableOperations()); // include is ignored
assertNull(resp.getAllowableOperationsOnTarget()); // include is ignored
// unauth access to file 1 content (via shared link)
response = getSingle(QuickShareLinkEntityResource.class, shared1Id + "/content", null, 200);
assertArrayEquals(file1_originalBytes, response.getResponseAsBytes());
Map<String, String> responseHeaders = response.getHeaders();
assertNotNull(responseHeaders);
assertEquals(file1_MimeType + ";charset=utf-8", responseHeaders.get("Content-Type"));
assertNotNull(responseHeaders.get("Expires"));
assertEquals("attachment; filename=\"" + fileName1 + "\"; filename*=UTF-8''" + fileName1 + "", responseHeaders.get("Content-Disposition"));
String lastModifiedHeader = responseHeaders.get(LAST_MODIFIED_HEADER);
assertNotNull(lastModifiedHeader);
// Test 304 response
Map<String, String> headers = Collections.singletonMap(IF_MODIFIED_SINCE_HEADER, lastModifiedHeader);
getSingle(URL_SHARED_LINKS, shared1Id + "/content", null, headers, 304);
// unauth access to file 1 content (via shared link) - without Content-Disposition header (attachment=false)
params = new HashMap<>();
params.put("attachment", "false");
response = getSingle(QuickShareLinkEntityResource.class, shared1Id + "/content", params, 200);
assertArrayEquals(file1_originalBytes, response.getResponseAsBytes());
responseHeaders = response.getHeaders();
assertNotNull(responseHeaders);
assertEquals(file1_MimeType + ";charset=utf-8", responseHeaders.get("Content-Type"));
assertNotNull(responseHeaders.get(LAST_MODIFIED_HEADER));
assertNotNull(responseHeaders.get("Expires"));
assertNull(responseHeaders.get("Content-Disposition"));
// -ve shared link rendition tests
{
// -ve test - try to get non-existent rendition content
getSingle(QuickShareLinkEntityResource.class, shared1Id + "/renditions/doclib/content", null, 404);
// -ve test - try to get unregistered rendition content
getSingle(QuickShareLinkEntityResource.class, shared1Id + "/renditions/dummy/content", null, 404);
}
// unauth access to get rendition info for a shared link (available => CREATED rendition only)
// -ve shared link rendition tests
{
// -ve test - try to get not created rendition for the given shared link
getSingle(QuickShareLinkEntityResource.class, shared1Id + "/renditions/doclib", null, 404);
// -ve test - try to get unregistered rendition
getSingle(QuickShareLinkEntityResource.class, shared1Id + "/renditions/dummy", null, 404);
}
// unauth access to get shared link renditions info (available => CREATED renditions only)
response = getAll(URL_SHARED_LINKS + "/" + shared1Id + "/renditions", null, 200);
List<Rendition> renditions = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Rendition.class);
assertEquals(0, renditions.size());
// create rendition of pdf doc - note: for some reason create rendition of txt doc fail on build m/c (TBC) ?
setRequestContext(user1);
Rendition rendition = createAndGetRendition(d1Id, "doclib");
assertNotNull(rendition);
assertEquals(Rendition.RenditionStatus.CREATED, rendition.getStatus());
setRequestContext(null);
// unauth access to get shared link renditions info (available => CREATED renditions only)
response = getAll(URL_SHARED_LINKS + "/" + shared1Id + "/renditions", null, 200);
renditions = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Rendition.class);
assertEquals(1, renditions.size());
assertEquals(Rendition.RenditionStatus.CREATED, renditions.get(0).getStatus());
assertEquals("doclib", renditions.get(0).getId());
// unauth access to get rendition info for a shared link (available => CREATED rendition only)
{
// get a created rendition for the given shared link
getSingle(QuickShareLinkEntityResource.class, shared1Id + "/renditions/doclib", null, 200);
}
// unauth access to get shared link file rendition content
response = getSingle(QuickShareLinkEntityResource.class, shared1Id + "/renditions/doclib/content", null, 200);
assertTrue(response.getResponseAsBytes().length > 0);
responseHeaders = response.getHeaders();
assertNotNull(responseHeaders);
assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG + ";charset=utf-8", responseHeaders.get("Content-Type"));
assertNotNull(responseHeaders.get(LAST_MODIFIED_HEADER));
assertNotNull(responseHeaders.get("Expires"));
String docName = "doclib";
assertEquals("attachment; filename=\"" + docName + "\"; filename*=UTF-8''" + docName + "", responseHeaders.get("Content-Disposition"));
// unauth access to get shared link file rendition content - without Content-Disposition header (attachment=false)
params = new HashMap<>();
params.put("attachment", "false");
response = getSingle(QuickShareLinkEntityResource.class, shared1Id + "/renditions/doclib/content", params, 200);
assertTrue(response.getResponseAsBytes().length > 0);
responseHeaders = response.getHeaders();
assertNotNull(responseHeaders);
assertEquals(MimetypeMap.MIMETYPE_IMAGE_PNG + ";charset=utf-8", responseHeaders.get("Content-Type"));
assertNotNull(responseHeaders.get("Expires"));
assertNull(responseHeaders.get("Content-Disposition"));
lastModifiedHeader = responseHeaders.get(LAST_MODIFIED_HEADER);
assertNotNull(lastModifiedHeader);
// Test 304 response
headers = Collections.singletonMap(IF_MODIFIED_SINCE_HEADER, lastModifiedHeader);
getSingle(URL_SHARED_LINKS, shared1Id + "/renditions/doclib/content", null, headers, 304);
// -ve test - userTwoN1 cannot delete shared link
setRequestContext(user2);
deleteSharedLink(shared1Id, 403);
// -ve test - unauthenticated
setRequestContext(null);
deleteSharedLink(shared1Id, 401);
// delete shared link
setRequestContext(user1);
deleteSharedLink(shared1Id);
}
/**
* Tests shared links to file with expiry date.
* <p>POST:</p>

View File

@@ -7,7 +7,7 @@
<parent>
<groupId>org.alfresco</groupId>
<artifactId>alfresco-community-repo</artifactId>
<version>20.114</version>
<version>20.140-SNAPSHOT</version>
</parent>
<dependencies>
@@ -565,69 +565,6 @@
</exclusions>
</dependency>
<!-- Keycloak dependencies -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-common</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-core</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-spi</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-servlet-adapter-spi</artifactId>
<version>${dependency.keycloak.version}</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- required by keycloak -->
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>${dependency.jboss.logging.version}</version>
</dependency>
<!-- Events dependencies -->
<dependency>
<groupId>org.alfresco</groupId>
@@ -872,6 +809,10 @@
<artifactId>reflections</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -1,28 +1,28 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2023 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.email.server;
import java.util.HashSet;
@@ -37,10 +37,7 @@ import org.alfresco.service.cmr.email.EmailMessageException;
import org.alfresco.service.cmr.email.EmailService;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.alfresco.util.PropertyCheck;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Base implementation of an email server.
@@ -62,13 +59,13 @@ public abstract class EmailServer extends AbstractLifecycleBean
private boolean requireTLS = false;
private boolean authenticate = false;
private EmailService emailService;
private AuthenticationComponent authenticationComponent;
private EmailService emailService;
private AuthenticationComponent authenticationComponent;
private String unknownUser;
protected EmailServer()
{
this.enabled = false;
protected EmailServer()
{
this.enabled = false;
this.port = 25;
this.domain = null;
this.maxConnections = 3;
@@ -301,60 +298,6 @@ public abstract class EmailServer extends AbstractLifecycleBean
}
}
private static volatile Boolean stop = false;
public static void main(String[] args)
{
if (args.length == 0)
{
usage();
return;
}
try (AbstractApplicationContext context = new ClassPathXmlApplicationContext(args))
{
if (!context.containsBean("emailServer"))
{
usage();
return;
}
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run()
{
stop = true;
synchronized (stop)
{
stop.notifyAll();
}
}
});
System.out.println("Use Ctrl-C to shutdown EmailServer");
while (!stop)
{
synchronized (stop)
{
stop.wait();
}
}
}
catch (BeansException e)
{
System.err.println("Error creating context: " + e);
usage();
}
catch (InterruptedException e)
{
}
}
private static void usage()
{
System.err.println("Use: EmailServer configLocation1, configLocation2, ...");
System.err.println("\t configLocation - spring xml configs with EmailServer related beans (emailServer, emailServerConfiguration, emailService)");
}
/**
* authenticate with a user/password
* @param userName

View File

@@ -1,101 +0,0 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.email.server;
import org.alfresco.email.server.impl.subetha.SubethaEmailMessage;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.email.EmailDelivery;
import org.alfresco.service.cmr.email.EmailMessage;
import org.alfresco.service.cmr.email.EmailService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.remoting.rmi.RmiClientInterceptor;
/**
* @author Michael Shavnev
* @since 2.2
*/
public class EmailServiceRemotable extends AbstractLifecycleBean implements EmailService
{
private String rmiRegistryHost;
private int rmiRegistryPort;
private EmailService emailServiceProxy;
public void setRmiRegistryHost(String rmiRegistryHost)
{
this.rmiRegistryHost = rmiRegistryHost;
}
public void setRmiRegistryPort(int rmiRegistryPort)
{
this.rmiRegistryPort = rmiRegistryPort;
}
public void importMessage(EmailDelivery delivery, EmailMessage message)
{
if (message instanceof SubethaEmailMessage)
{
((SubethaEmailMessage) message).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
}
emailServiceProxy.importMessage(delivery, message);
}
public void importMessage(EmailDelivery delivery, NodeRef nodeRef, EmailMessage message)
{
if (message instanceof SubethaEmailMessage)
{
((SubethaEmailMessage) message).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
}
emailServiceProxy.importMessage(delivery, nodeRef, message);
}
@Override
protected void onBootstrap(ApplicationEvent event)
{
if (rmiRegistryHost == null)
{
throw new AlfrescoRuntimeException("Property 'rmiRegistryHost' not set");
}
if (rmiRegistryPort == 0)
{
throw new AlfrescoRuntimeException("Property 'rmiRegistryPort' not set");
}
RmiClientInterceptor rmiClientInterceptor = new RmiClientInterceptor();
rmiClientInterceptor.setRefreshStubOnConnectFailure(true);
rmiClientInterceptor.setServiceUrl("rmi://" + rmiRegistryHost + ":" + rmiRegistryPort + "/emailService");
emailServiceProxy = (EmailService) ProxyFactory.getProxy(EmailService.class, rmiClientInterceptor);
}
@Override
protected void onShutdown(ApplicationEvent event)
{
}
}

View File

@@ -1,35 +1,34 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2023 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.email.server.impl.subetha;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -417,29 +416,12 @@ public class SubethaEmailMessage implements EmailMessage
}
return fileName;
}
public void setRmiRegistry(String rmiRegistryHost, int rmiRegistryPort)
{
if (body instanceof SubethaEmailMessagePart)
{
((SubethaEmailMessagePart) body).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
}
for (EmailMessagePart attachment : attachments)
{
if (attachment instanceof SubethaEmailMessagePart)
{
((SubethaEmailMessagePart) attachment).setRmiRegistry(rmiRegistryHost, rmiRegistryPort);
}
}
}
public List<String> getCC()
{
return cc;
}
public String getFrom()
{
return from;

View File

@@ -1,34 +1,31 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2023 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.email.server.impl.subetha;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -39,7 +36,6 @@ import javax.mail.Part;
import org.alfresco.service.cmr.email.EmailMessageException;
import org.alfresco.service.cmr.email.EmailMessagePart;
import org.springframework.extensions.surf.util.ParameterCheck;
import org.alfresco.util.remote.RemotableInputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -64,9 +60,6 @@ public class SubethaEmailMessagePart implements EmailMessagePart
private String contentType;
private InputStream contentInputStream;
private String rmiRegistryHost;
private int rmiRegistryPort;
protected SubethaEmailMessagePart()
{
super();
@@ -145,20 +138,4 @@ public class SubethaEmailMessagePart implements EmailMessagePart
}
public void setRmiRegistry(String rmiRegistryHost, int rmiRegistryPort)
{
this.rmiRegistryHost = rmiRegistryHost;
this.rmiRegistryPort = rmiRegistryPort;
}
private void writeObject(ObjectOutputStream out) throws IOException
{
contentInputStream = new RemotableInputStream(rmiRegistryHost, rmiRegistryPort, contentInputStream);
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
in.defaultReadObject();
}
}

View File

@@ -25,6 +25,7 @@
*/
package org.alfresco.repo.content.transform;
import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.repo.content.metadata.AsynchronousExtractor;
import org.alfresco.repo.rendition2.RenditionDefinition2;
import org.alfresco.service.cmr.repository.ContentReader;
@@ -60,11 +61,12 @@ public class LocalTransformImpl extends AbstractLocalTransform
boolean retryTransformOnDifferentMimeType,
Set<TransformOption> transformsTransformOptions,
LocalTransformServiceRegistry localTransformServiceRegistry, String baseUrl,
HttpClientConfig httpClientConfig,
int startupRetryPeriodSeconds)
{
super(name, transformerDebug, mimetypeService, strictMimeTypeCheck, strictMimetypeExceptions,
retryTransformOnDifferentMimeType, transformsTransformOptions, localTransformServiceRegistry);
remoteTransformerClient = new RemoteTransformerClient(name, baseUrl);
remoteTransformerClient = new RemoteTransformerClient(name, baseUrl, httpClientConfig);
remoteTransformerClient.setStartupRetryPeriodSeconds(startupRetryPeriodSeconds);
checkAvailability();

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2019 - 2022 Alfresco Software Limited
* Copyright (C) 2019 - 2023 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
@@ -35,6 +35,8 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.alfresco.httpclient.HttpClient4Factory;
import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.transform.config.CoreFunction;
import org.alfresco.transform.config.TransformOptionGroup;
@@ -77,6 +79,17 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
private boolean strictMimeTypeCheck;
private Map<String, Set<String>> strictMimetypeExceptions;
private boolean retryTransformOnDifferentMimeType;
private HttpClientConfig httpClientConfig;
public HttpClientConfig getHttpClientConfig()
{
return httpClientConfig;
}
public void setHttpClientConfig(HttpClientConfig httpClientConfig)
{
this.httpClientConfig = httpClientConfig;
}
public void setPipelineConfigDir(String pipelineConfigDir)
{
@@ -134,7 +147,7 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
@Override
public boolean readConfig() throws IOException
{
CombinedConfig combinedConfig = new CombinedConfig(getLog(), this);
CombinedConfig combinedConfig = new CombinedConfig(getLog(), this, httpClientConfig);
List<String> urls = getTEngineUrlsSortedByName();
boolean successReadingConfig = combinedConfig.addRemoteConfig(urls, "T-Engine");
successReadingConfig &= combinedConfig.addLocalConfig("alfresco/transforms");
@@ -188,7 +201,8 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
int startupRetryPeriodSeconds = getStartupRetryPeriodSeconds(name);
localTransform = new LocalTransformImpl(name, transformerDebug, mimetypeService,
strictMimeTypeCheck, strictMimetypeExceptions, retryTransformOnDifferentMimeType,
transformsTransformOptions, this, baseUrl, startupRetryPeriodSeconds);
transformsTransformOptions, this, baseUrl, httpClientConfig,
startupRetryPeriodSeconds);
}
else if (isPipeline)
{

View File

@@ -30,6 +30,8 @@ import java.io.InputStream;
import java.util.StringJoiner;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.httpclient.HttpClient4Factory;
import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.Pair;
@@ -45,7 +47,6 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
/**
@@ -57,6 +58,8 @@ import org.apache.http.util.EntityUtils;
*/
public class RemoteTransformerClient
{
private final HttpClientConfig httpClientConfig;
private final String name;
private final String baseUrl;
@@ -70,10 +73,11 @@ public class RemoteTransformerClient
// Only changed once on success. This is stored so it can always be returned.
private Pair<Boolean, String> checkResult = new Pair<>(null, null);
public RemoteTransformerClient(String name, String baseUrl)
public RemoteTransformerClient(String name, String baseUrl, HttpClientConfig httpClientConfig)
{
this.name = name;
this.baseUrl = baseUrl == null || baseUrl.trim().isEmpty() ? null : baseUrl.trim();
this.httpClientConfig = httpClientConfig;
}
public void setStartupRetryPeriodSeconds(int startupRetryPeriodSeconds)
@@ -129,7 +133,7 @@ public class RemoteTransformerClient
try
{
try (CloseableHttpClient httpclient = HttpClients.createDefault())
try (CloseableHttpClient httpclient = HttpClient4Factory.createHttpClient(httpClientConfig))
{
try (CloseableHttpResponse response = execute(httpclient, httppost))
{
@@ -232,7 +236,7 @@ public class RemoteTransformerClient
try
{
try (CloseableHttpClient httpclient = HttpClients.createDefault())
try (CloseableHttpClient httpclient = HttpClient4Factory.createHttpClient(httpClientConfig))
{
try (CloseableHttpResponse response = execute(httpclient, httpGet))
{

View File

@@ -504,6 +504,21 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
return UrlUtil.replaceShareUrlPlaceholder(url, sysAdminParams);
}
private String getRepoBaseUrl(String url, String propName)
{
if (url == null)
{
LOGGER.warn("The url for the property [" + propName + "] is not configured.");
return "";
}
if (url.endsWith("/"))
{
url = url.substring(0, url.length() - 1);
}
return UrlUtil.replaceRepoBaseUrlPlaceholder(url, sysAdminParams);
}
protected String getResetPasswordEmailTemplate(ClientApp clientApp)
{
return clientApp.getProperty("requestResetPasswordTemplatePath");
@@ -522,7 +537,16 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
StringBuilder sb = new StringBuilder(100);
String pageUrl = clientApp.getProperty("resetPasswordPageUrl");
if (StringUtils.isEmpty(pageUrl))
if(!StringUtils.isEmpty(clientApp.getProperty("workspaceUrl")))
{
String workspaceUrlPlaceholder = clientApp.getProperty("workspaceUrl");
String workSpaceUrl = getRepoBaseUrl(workspaceUrlPlaceholder,"");
sb.append(UrlUtil.replaceWorkSpaceUrlPlaceholder(pageUrl,workSpaceUrl));
LOGGER.warn("Client Name is " + clientApp.getName() + " The url used is " + sb.toString());
}
else if(StringUtils.isEmpty(pageUrl))
{
sb.append(UrlUtil.getShareUrl(sysAdminParams));
@@ -535,7 +559,7 @@ public class ResetPasswordServiceImpl implements ResetPasswordService
sb.append(getUrl(pageUrl, ""));
}
sb.append("?key=").append(key)
sb.append("?key=").append(key)
.append("&id=").append(BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, id));
return sb.toString();

View File

@@ -28,7 +28,8 @@ package org.alfresco.repo.security.authentication.identityservice;
import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AbstractAuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.CredentialsVerificationException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -76,12 +77,12 @@ public class IdentityServiceAuthenticationComponent extends AbstractAuthenticati
try
{
// Attempt to verify user credentials
identityServiceFacade.verifyCredentials(userName, new String(password));
identityServiceFacade.authorize(AuthorizationGrant.password(userName, new String(password)));
// Verification was successful so treat as authenticated user
setCurrentUser(userName);
}
catch (CredentialsVerificationException e)
catch (IdentityServiceFacadeException e)
{
throw new AuthenticationException("Failed to verify user credentials against the OAuth2 Authorization Server.", e);
}

View File

@@ -25,15 +25,8 @@
*/
package org.alfresco.repo.security.authentication.identityservice;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.TreeMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.util.UriComponentsBuilder;
/**
@@ -41,23 +34,28 @@ import org.springframework.web.util.UriComponentsBuilder;
*
* @author Gavin Cornwell
*/
public class IdentityServiceConfig extends AdapterConfig implements InitializingBean
public class IdentityServiceConfig
{
private static final Log LOGGER = LogFactory.getLog(IdentityServiceConfig.class);
private static final String REALMS = "realms";
private static final String SECRET = "secret";
private static final String CREDENTIALS_SECRET = "identity-service.credentials.secret";
private static final String CREDENTIALS_PROVIDER = "identity-service.credentials.provider";
private Properties globalProperties;
private int clientConnectionTimeout;
private int clientSocketTimeout;
public void setGlobalProperties(Properties globalProperties)
{
this.globalProperties = globalProperties;
}
// client id
private String resource;
private String clientSecret;
private String authServerUrl;
private String realm;
private int connectionPoolSize;
private boolean allowAnyHostname;
private boolean disableTrustManager;
private String truststore;
private String truststorePassword;
private String clientKeystore;
private String clientKeystorePassword;
private String clientKeyPassword;
private String realmKey;
private int publicKeyCacheTtl;
private boolean publicClient;
/**
*
@@ -94,52 +92,163 @@ public class IdentityServiceConfig extends AdapterConfig implements Initializing
{
this.clientSocketTimeout = clientSocketTimeout;
}
@Override
public void afterPropertiesSet() throws Exception
public void setConnectionPoolSize(int connectionPoolSize)
{
// programmatically build the more complex objects i.e. credentials
Map<String, Object> credentials = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
String secret = this.globalProperties.getProperty(CREDENTIALS_SECRET);
if (secret != null && !secret.isEmpty())
{
credentials.put(SECRET, secret);
}
String provider = this.globalProperties.getProperty(CREDENTIALS_PROVIDER);
if (provider != null && !provider.isEmpty())
{
credentials.put("provider", provider);
}
// TODO: add support for redirect-rewrite-rules and policy-enforcer if and when we need to support it
if (!credentials.isEmpty())
{
this.setCredentials(credentials);
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Created credentials map from config: " + credentials);
}
}
this.connectionPoolSize = connectionPoolSize;
}
String getIssuerUrl()
public int getConnectionPoolSize()
{
return UriComponentsBuilder.fromUriString(getAuthServerUrl())
.pathSegment(REALMS, getRealm())
.build()
.toString();
return connectionPoolSize;
}
public String getAuthServerUrl()
{
return authServerUrl;
}
public void setAuthServerUrl(String authServerUrl)
{
this.authServerUrl = authServerUrl;
}
public String getRealm()
{
return realm;
}
public void setRealm(String realm)
{
this.realm = realm;
}
public String getResource()
{
return resource;
}
public void setResource(String resource)
{
this.resource = resource;
}
public void setClientSecret(String clientSecret)
{
this.clientSecret = clientSecret;
}
public String getClientSecret()
{
return Optional.ofNullable(getCredentials())
.map(c -> c.get(SECRET))
.filter(String.class::isInstance)
.map(String.class::cast)
.orElse("");
return Optional.ofNullable(clientSecret)
.orElse("");
}
public String getIssuerUrl()
{
return UriComponentsBuilder.fromUriString(getAuthServerUrl())
.pathSegment(REALMS, getRealm())
.build()
.toString();
}
public void setAllowAnyHostname(boolean allowAnyHostname)
{
this.allowAnyHostname = allowAnyHostname;
}
public boolean isAllowAnyHostname()
{
return allowAnyHostname;
}
public void setDisableTrustManager(boolean disableTrustManager)
{
this.disableTrustManager = disableTrustManager;
}
public boolean isDisableTrustManager()
{
return disableTrustManager;
}
public void setTruststore(String truststore)
{
this.truststore = truststore;
}
public String getTruststore()
{
return truststore;
}
public void setTruststorePassword(String truststorePassword)
{
this.truststorePassword = truststorePassword;
}
public String getTruststorePassword()
{
return truststorePassword;
}
public void setClientKeystore(String clientKeystore)
{
this.clientKeystore = clientKeystore;
}
public String getClientKeystore()
{
return clientKeystore;
}
public void setClientKeystorePassword(String clientKeystorePassword)
{
this.clientKeystorePassword = clientKeystorePassword;
}
public String getClientKeystorePassword()
{
return clientKeystorePassword;
}
public void setClientKeyPassword(String clientKeyPassword)
{
this.clientKeyPassword = clientKeyPassword;
}
public String getClientKeyPassword()
{
return clientKeyPassword;
}
public void setRealmKey(String realmKey)
{
this.realmKey = realmKey;
}
public String getRealmKey()
{
return realmKey;
}
public void setPublicKeyCacheTtl(int publicKeyCacheTtl)
{
this.publicKeyCacheTtl = publicKeyCacheTtl;
}
public int getPublicKeyCacheTtl()
{
return publicKeyCacheTtl;
}
public void setPublicClient(boolean publicClient)
{
this.publicClient = publicClient;
}
public boolean isPublicClient()
{
return publicClient;
}
}

View File

@@ -25,34 +25,36 @@
*/
package org.alfresco.repo.security.authentication.identityservice;
import java.util.Optional;
import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
import java.time.Instant;
import java.util.Objects;
/**
* Allows to interact with the Identity Service
*/
interface IdentityServiceFacade
public interface IdentityServiceFacade
{
/**
* Verifies provided user credentials. The OAuth2's Client role is only used to verify the user credentials (Resource Owner Password
* Credentials Flow) this is why there is an explicit method for verifying these.
*
* @param username user's name
* @param password user's password
* @throws CredentialsVerificationException when the verification failed or couldn't be performed
* Returns {@link AccessToken} based authorization for provided {@link AuthorizationGrant}.
* @param grant the OAuth2 grant provided by the Resource Owner.
* @return {@link AccessTokenAuthorization} containing access token and optional refresh token.
* @throws {@link AuthorizationException} when provided grant cannot be exchanged for the access token.
*/
void verifyCredentials(String username, String password);
AccessTokenAuthorization authorize(AuthorizationGrant grant) throws AuthorizationException;
/**
* Extracts username from provided token
*
* @param token token representation
* @return possible username
* Decodes the access token into the {@link DecodedAccessToken} which contains claims connected with a given token.
* @param token {@link String} with encoded access token value.
* @return {@link DecodedAccessToken} containing decoded claims.
* @throws {@link TokenDecodingException} when token decoding failed.
*/
Optional<String> extractUsernameFromToken(String token);
DecodedAccessToken decodeToken(String token) throws TokenDecodingException;
class IdentityServiceFacadeException extends RuntimeException
{
IdentityServiceFacadeException(String message)
public IdentityServiceFacadeException(String message)
{
super(message);
}
@@ -62,29 +64,149 @@ interface IdentityServiceFacade
super(message, cause);
}
}
class CredentialsVerificationException extends IdentityServiceFacadeException
class AuthorizationException extends IdentityServiceFacadeException
{
CredentialsVerificationException(String message)
AuthorizationException(String message)
{
super(message);
}
CredentialsVerificationException(String message, Throwable cause)
AuthorizationException(String message, Throwable cause)
{
super(message, cause);
}
}
class TokenException extends IdentityServiceFacadeException
class TokenDecodingException extends IdentityServiceFacadeException
{
TokenException(String message)
TokenDecodingException(String message)
{
super(message);
}
TokenException(String message, Throwable cause)
TokenDecodingException(String message, Throwable cause)
{
super(message, cause);
}
}
}
/**
* Represents access token authorization with optional refresh token.
*/
interface AccessTokenAuthorization
{
/**
* Required {@link AccessToken}
* @return {@link AccessToken}
*/
AccessToken getAccessToken();
/**
* Optional refresh token.
* @return Refresh token or {@code null}
*/
String getRefreshTokenValue();
}
interface AccessToken {
String getTokenValue();
Instant getExpiresAt();
}
interface DecodedAccessToken extends AccessToken
{
Object getClaim(String claim);
}
class AuthorizationGrant {
private final String username;
private final String password;
private final String refreshToken;
private final String authorizationCode;
private final String redirectUri;
private AuthorizationGrant(String username, String password, String refreshToken, String authorizationCode, String redirectUri)
{
this.username = username;
this.password = password;
this.refreshToken = refreshToken;
this.authorizationCode = authorizationCode;
this.redirectUri = redirectUri;
}
public static AuthorizationGrant password(String username, String password)
{
return new AuthorizationGrant(requireNonNull(username), requireNonNull(password), null, null, null);
}
public static AuthorizationGrant refreshToken(String refreshToken)
{
return new AuthorizationGrant(null, null, requireNonNull(refreshToken), null, null);
}
public static AuthorizationGrant authorizationCode(String authorizationCode, String redirectUri)
{
return new AuthorizationGrant(null, null, null, requireNonNull(authorizationCode), requireNonNull(redirectUri));
}
boolean isPassword()
{
return nonNull(username);
}
boolean isRefreshToken()
{
return nonNull(refreshToken);
}
boolean isAuthorizationCode()
{
return nonNull(authorizationCode);
}
String getUsername()
{
return username;
}
String getPassword()
{
return password;
}
String getRefreshToken()
{
return refreshToken;
}
String getAuthorizationCode()
{
return authorizationCode;
}
String getRedirectUri()
{
return redirectUri;
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuthorizationGrant that = (AuthorizationGrant) o;
return Objects.equals(username, that.username) &&
Objects.equals(password, that.password) &&
Objects.equals(refreshToken, that.refreshToken) &&
Objects.equals(authorizationCode, that.authorizationCode) &&
Objects.equals(redirectUri, that.redirectUri);
}
@Override
public int hashCode()
{
return Objects.hash(username, password, refreshToken, authorizationCode, redirectUri);
}
}
}

View File

@@ -27,50 +27,81 @@ package org.alfresco.repo.security.authentication.identityservice;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static java.util.function.Predicate.not;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.source.DefaultJWKSetCache;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.util.ResourceRetriever;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.TrustAllStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder.PasswordGrantBuilder;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
import org.springframework.security.converter.RsaKeyConverters;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
/**
*
@@ -91,14 +122,18 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
public void setIdentityServiceConfig(IdentityServiceConfig identityServiceConfig)
{
factory = new SpringBasedIdentityServiceFacadeFactory(identityServiceConfig);
factory = new SpringBasedIdentityServiceFacadeFactory(
new HttpClientProvider(identityServiceConfig)::createHttpClient,
new ClientRegistrationProvider(identityServiceConfig)::createClientRegistration,
new JwtDecoderProvider(identityServiceConfig)::createJwtDecoder
);
}
@Override
public IdentityServiceFacade getObject() throws Exception
{
// The creation of the client can be disabled for testing or when the username/password authentication is not required,
// for instance when Keycloak is configured for 'bearer only' authentication or Direct Access Grants are disabled.
// for instance when Identity Service is configured for 'bearer only' authentication or Direct Access Grants are disabled.
if (!enabled)
{
return null;
@@ -137,15 +172,15 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
}
@Override
public void verifyCredentials(String username, String password)
public AccessTokenAuthorization authorize(AuthorizationGrant grant) throws AuthorizationException
{
getTargetFacade().verifyCredentials(username, password);
return getTargetFacade().authorize(grant);
}
@Override
public Optional<String> extractUsernameFromToken(String token)
public DecodedAccessToken decodeToken(String token) throws TokenDecodingException
{
return getTargetFacade().extractUsernameFromToken(token);
return getTargetFacade().decodeToken(token);
}
private IdentityServiceFacade getTargetFacade()
@@ -175,12 +210,18 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
private static class SpringBasedIdentityServiceFacadeFactory
{
private static final long CLOCK_SKEW_MS = 0;
private final IdentityServiceConfig config;
private final Supplier<HttpClient> httpClientProvider;
private final Function<RestOperations, ClientRegistration> clientRegistrationProvider;
private final BiFunction<RestOperations, ProviderDetails, JwtDecoder> jwtDecoderProvider;
SpringBasedIdentityServiceFacadeFactory(IdentityServiceConfig config)
SpringBasedIdentityServiceFacadeFactory(
Supplier<HttpClient> httpClientProvider,
Function<RestOperations, ClientRegistration> clientRegistrationProvider,
BiFunction<RestOperations, ProviderDetails, JwtDecoder> jwtDecoderProvider)
{
this.config = Objects.requireNonNull(config);
this.httpClientProvider = Objects.requireNonNull(httpClientProvider);
this.clientRegistrationProvider = Objects.requireNonNull(clientRegistrationProvider);
this.jwtDecoderProvider = Objects.requireNonNull(jwtDecoderProvider);
}
private IdentityServiceFacade createIdentityServiceFacade()
@@ -188,201 +229,325 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
//Here we preserve the behaviour of previously used Keycloak Adapter
// * Client is authenticating itself using basic auth
// * Resource Owner Password Credentials Flow is used to authenticate Resource Owner
// * There is no caching of authenticated clients (NoStoredAuthorizedClient)
// * There is only one Authorization Server/Client pair (SingleClientRegistration)
final RestTemplate restTemplate = createRestTemplate();
final ClientRegistration clientRegistration = createClientRegistration(restTemplate);
final OAuth2AuthorizedClientManager clientManager = createAuthorizedClientManager(restTemplate, clientRegistration);
final JwtDecoder jwtDecoder = createJwtDecoder(clientRegistration);
final ClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClientProvider.get());
final RestTemplate restTemplate = new RestTemplate(httpRequestFactory);
final ClientRegistration clientRegistration = clientRegistrationProvider.apply(restTemplate);
final JwtDecoder jwtDecoder = jwtDecoderProvider.apply(restTemplate, clientRegistration.getProviderDetails());
return new SpringBasedIdentityServiceFacade(clientManager, jwtDecoder);
return new SpringBasedIdentityServiceFacade(createOAuth2RestTemplate(httpRequestFactory), clientRegistration, jwtDecoder);
}
private RestTemplate createRestTemplate()
private RestTemplate createOAuth2RestTemplate(ClientHttpRequestFactory requestFactory)
{
final SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(config.getClientConnectionTimeout());
requestFactory.setReadTimeout(config.getClientSocketTimeout());
final RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
restTemplate.setRequestFactory(requestFactory);
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
return restTemplate;
}
}
private ClientRegistration createClientRegistration(RestTemplate restTemplate)
private static class HttpClientProvider
{
private final IdentityServiceConfig config;
private HttpClientProvider(IdentityServiceConfig config)
{
this.config = Objects.requireNonNull(config);
}
private HttpClient createHttpClient()
{
try
{
return ClientRegistrations
.fromIssuerLocation(config.getIssuerUrl())
.clientId(config.getResource())
.clientSecret(config.getClientSecret())
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.registrationId(SpringBasedIdentityServiceFacade.CLIENT_REGISTRATION_ID)
.build();
HttpClientBuilder clientBuilder = HttpClients.custom();
applyConnectionConfiguration(clientBuilder);
applySSLConfiguration(clientBuilder);
return clientBuilder.build();
}
catch (RuntimeException e)
catch (Exception e)
{
LOGGER.warn("Failed to create ClientRegistration.", e);
throw authorizationServerCantBeUsedException(e);
throw new IllegalStateException("Failed to create ClientHttpRequestFactory. " + e.getMessage(), e);
}
}
private OAuth2AuthorizedClientManager createAuthorizedClientManager(RestTemplate restTemplate, ClientRegistration clientRegistration)
private void applyConnectionConfiguration(HttpClientBuilder builder)
{
final AuthorizedClientServiceOAuth2AuthorizedClientManager manager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
new SingleClientRegistration(clientRegistration),
new NoStoredAuthorizedClient());
final RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(config.getClientConnectionTimeout())
.setSocketTimeout(config.getClientSocketTimeout())
.build();
final Consumer<PasswordGrantBuilder> passwordGrantConfigurer = b -> {
final DefaultPasswordTokenResponseClient client = new DefaultPasswordTokenResponseClient();
client.setRestOperations(restTemplate);
b.accessTokenResponseClient(client);
b.clockSkew(Duration.of(CLOCK_SKEW_MS, ChronoUnit.MILLIS));
};
manager.setAuthorizedClientProvider(OAuth2AuthorizedClientProviderBuilder.builder()
.password(passwordGrantConfigurer)
.build());
manager.setContextAttributesMapper(OAuth2AuthorizeRequest::getAttributes);
return manager;
builder.setDefaultRequestConfig(requestConfig)
.setMaxConnTotal(config.getConnectionPoolSize());
}
private JwtDecoder createJwtDecoder(ClientRegistration clientRegistration)
private void applySSLConfiguration(HttpClientBuilder builder) throws Exception
{
final OidcIdTokenDecoderFactory decoderFactory = new OidcIdTokenDecoderFactory();
decoderFactory.setJwtValidatorFactory(c -> new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(Duration.of(CLOCK_SKEW_MS, ChronoUnit.MILLIS)),
new JwtIssuerValidator(c.getProviderDetails().getIssuerUri()),
new JwtClaimValidator<String>("typ", "Bearer"::equals),
new JwtClaimValidator<String>(JwtClaimNames.SUB, Objects::nonNull)
SSLContextBuilder sslContextBuilder = null;
if (config.isDisableTrustManager())
{
sslContextBuilder = SSLContexts.custom()
.loadTrustMaterial(TrustAllStrategy.INSTANCE);
));
}
else if (isDefined(config.getTruststore()))
{
final char[] truststorePassword = asCharArray(config.getTruststorePassword(), null);
sslContextBuilder = SSLContexts.custom()
.loadTrustMaterial(new File(config.getTruststore()), truststorePassword);
}
if (isDefined(config.getClientKeystore()))
{
if (sslContextBuilder == null)
{
sslContextBuilder = SSLContexts.custom();
}
final char[] keystorePassword = asCharArray(config.getClientKeystorePassword(), null);
final char[] keyPassword = asCharArray(config.getClientKeyPassword(), keystorePassword);
sslContextBuilder.loadKeyMaterial(new File(config.getClientKeystore()), keystorePassword, keyPassword);
}
if (sslContextBuilder != null)
{
builder.setSSLContext(sslContextBuilder.build());
}
if (config.isDisableTrustManager() || config.isAllowAnyHostname())
{
builder.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE);
}
}
private char[] asCharArray(String value, char[] nullValue)
{
return Optional.ofNullable(value)
.filter(not(String::isBlank))
.map(String::toCharArray)
.orElse(nullValue);
}
}
private static class ClientRegistrationProvider
{
private final IdentityServiceConfig config;
private ClientRegistrationProvider(IdentityServiceConfig config)
{
this.config = Objects.requireNonNull(config);
}
public ClientRegistration createClientRegistration(final RestOperations rest)
{
return possibleMetadataURIs()
.stream()
.map(u -> extractMetadata(rest, u))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst()
.map(this::createBuilder)
.map(this::configureClientAuthentication)
.map(Builder::build)
.orElseThrow(() -> new IllegalStateException("Failed to create ClientRegistration."));
}
private ClientRegistration.Builder createBuilder(OIDCProviderMetadata metadata)
{
return ClientRegistration
.withRegistrationId("ids")
.tokenUri(metadata.getTokenEndpointURI().toASCIIString())
.jwkSetUri(metadata.getJWKSetURI().toASCIIString())
.issuerUri(config.getIssuerUrl())
.authorizationGrantType(AuthorizationGrantType.PASSWORD);
}
private Builder configureClientAuthentication(Builder builder)
{
builder.clientId(config.getResource());
if (config.isPublicClient())
{
return builder.clientSecret(null)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST);
}
return builder.clientSecret(config.getClientSecret())
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
}
private Optional<OIDCProviderMetadata> extractMetadata(RestOperations rest, URI metadataUri)
{
final String response;
try
{
return decoderFactory.createDecoder(clientRegistration);
final ResponseEntity<String> r = rest.exchange(RequestEntity.get(metadataUri).build(), String.class);
if (r.getStatusCode() != HttpStatus.OK || !r.hasBody())
{
LOGGER.warn("Unexpected response from " + metadataUri + ". Status code: " + r.getStatusCode() + ", has body: " + r.hasBody() + ".");
return Optional.empty();
}
response = r.getBody();
}
catch (RuntimeException e)
catch (Exception e)
{
LOGGER.warn("Failed to get response from " + metadataUri + ". " + e.getMessage(), e);
return Optional.empty();
}
try
{
return Optional.of(OIDCProviderMetadata.parse(response));
}
catch (Exception e)
{
LOGGER.warn("Failed to parse metadata. " + e.getMessage(), e);
return Optional.empty();
}
}
private Collection<URI> possibleMetadataURIs()
{
return List.of(UriComponentsBuilder.fromUriString(config.getIssuerUrl())
.pathSegment(".well-known", "openid-configuration")
.build().toUri());
}
}
static class JwtDecoderProvider
{
private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.RS256;
private final IdentityServiceConfig config;
JwtDecoderProvider(IdentityServiceConfig config)
{
this.config = Objects.requireNonNull(config);
}
public JwtDecoder createJwtDecoder(RestOperations rest, ProviderDetails providerDetails)
{
try
{
final NimbusJwtDecoder decoder = buildJwtDecoder(rest, providerDetails);
decoder.setJwtValidator(createJwtTokenValidator(providerDetails));
decoder.setClaimSetConverter(new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()));
return decoder;
} catch (RuntimeException e)
{
LOGGER.warn("Failed to create JwtDecoder.", e);
throw authorizationServerCantBeUsedException(e);
}
}
private static class NoStoredAuthorizedClient implements OAuth2AuthorizedClientService
private NimbusJwtDecoder buildJwtDecoder(RestOperations rest, ProviderDetails providerDetails)
{
@Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, String principalName)
if (isDefined(config.getRealmKey()))
{
return null;
final RSAPublicKey publicKey = parsePublicKey(config.getRealmKey());
return NimbusJwtDecoder.withPublicKey(publicKey)
.signatureAlgorithm(SIGNATURE_ALGORITHM)
.build();
}
@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal)
final String jwkSetUri = requireValidJwkSetUri(providerDetails);
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
.jwsAlgorithm(SIGNATURE_ALGORITHM)
.restOperations(rest)
.jwtProcessorCustomizer(this::reconfigureJWKSCache)
.build();
}
private void reconfigureJWKSCache(ConfigurableJWTProcessor<SecurityContext> jwtProcessor)
{
final Optional<RemoteJWKSet<SecurityContext>> jwkSource = ofNullable(jwtProcessor)
.map(ConfigurableJWTProcessor::getJWSKeySelector)
.filter(JWSVerificationKeySelector.class::isInstance).map(o -> (JWSVerificationKeySelector<SecurityContext>)o)
.map(JWSVerificationKeySelector::getJWKSource)
.filter(RemoteJWKSet.class::isInstance).map(o -> (RemoteJWKSet<SecurityContext>)o);
if (jwkSource.isEmpty())
{
//do nothing
LOGGER.warn("Not able to reconfigure the JWK Cache. Unexpected JWKSource.");
return;
}
@Override
public void removeAuthorizedClient(String clientRegistrationId, String principalName)
final Optional<URL> jwkSetUrl = jwkSource.map(RemoteJWKSet::getJWKSetURL);
if (jwkSetUrl.isEmpty())
{
//do nothing
LOGGER.warn("Not able to reconfigure the JWK Cache. Unknown JWKSetURL.");
return;
}
final Optional<ResourceRetriever> resourceRetriever = jwkSource.map(RemoteJWKSet::getResourceRetriever);
if (resourceRetriever.isEmpty())
{
LOGGER.warn("Not able to reconfigure the JWK Cache. Unknown ResourceRetriever.");
return;
}
final DefaultJWKSetCache cache = new DefaultJWKSetCache(config.getPublicKeyCacheTtl(), -1, TimeUnit.SECONDS);
final JWKSource<SecurityContext> cachingJWKSource = new RemoteJWKSet<>(jwkSetUrl.get(), resourceRetriever.get(), cache);
jwtProcessor.setJWSKeySelector(new JWSVerificationKeySelector<>(
JWSAlgorithm.parse(SIGNATURE_ALGORITHM.getName()),
cachingJWKSource));
}
private OAuth2TokenValidator<Jwt> createJwtTokenValidator(ProviderDetails providerDetails)
{
return new DelegatingOAuth2TokenValidator<>(
new JwtTimestampValidator(Duration.of(0, ChronoUnit.MILLIS)),
new JwtIssuerValidator(providerDetails.getIssuerUri()),
new JwtClaimValidator<String>("typ", "Bearer"::equals),
new JwtClaimValidator<String>(JwtClaimNames.SUB, Objects::nonNull));
}
private RSAPublicKey parsePublicKey(String pem)
{
try
{
return tryToParsePublicKey(pem);
}
catch (Exception e)
{
if (isPemFormatException(e))
{
//For backward compatibility with Keycloak adapter
return tryToParsePublicKey("-----BEGIN PUBLIC KEY-----\n" + pem + "\n-----END PUBLIC KEY-----");
}
throw e;
}
}
private static class SingleClientRegistration implements ClientRegistrationRepository
private RSAPublicKey tryToParsePublicKey(String pem)
{
private final ClientRegistration clientRegistration;
final InputStream pemStream = new ByteArrayInputStream(pem.getBytes(StandardCharsets.UTF_8));
return RsaKeyConverters.x509().convert(pemStream);
}
private SingleClientRegistration(ClientRegistration clientRegistration)
{
this.clientRegistration = requireNonNull(clientRegistration);
}
private boolean isPemFormatException(Exception e)
{
return e.getMessage() != null && e.getMessage().contains("-----BEGIN PUBLIC KEY-----");
}
@Override
public ClientRegistration findByRegistrationId(String registrationId)
{
return Objects.equals(registrationId, clientRegistration.getRegistrationId()) ? clientRegistration : null;
private String requireValidJwkSetUri(ProviderDetails providerDetails)
{
final String uri = providerDetails.getJwkSetUri();
if (!isDefined(uri)) {
OAuth2Error oauth2Error = new OAuth2Error("missing_signature_verifier",
"Failed to find a Signature Verifier for: '"
+ providerDetails.getIssuerUri()
+ "'. Check to ensure you have configured the JwkSet URI.",
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
return uri;
}
}
static class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
private static boolean isDefined(String value)
{
static final String CLIENT_REGISTRATION_ID = "ids";
private final OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager;
private JwtDecoder jwtDecoder;
SpringBasedIdentityServiceFacade(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager, JwtDecoder jwtDecoder)
{
this.oAuth2AuthorizedClientManager = requireNonNull(oAuth2AuthorizedClientManager);
this.jwtDecoder = requireNonNull(jwtDecoder);
}
@Override
public void verifyCredentials(String username, String password)
{
final OAuth2AuthorizedClient authorizedClient;
try
{
final OAuth2AuthorizeRequest authRequest = createPasswordCredentialsRequest(username, password);
authorizedClient = oAuth2AuthorizedClientManager.authorize(authRequest);
}
catch (OAuth2AuthorizationException e)
{
LOGGER.debug("Failed to authorize against Authorization Server. Reason: " + e.getError() + ".");
throw new CredentialsVerificationException("Authorization against the Authorization Server failed with " + e.getError() + ".", e);
}
catch (RuntimeException e)
{
LOGGER.warn("Failed to authorize against Authorization Server. Reason: " + e.getMessage());
throw new CredentialsVerificationException("Failed to authorize against Authorization Server.", e);
}
if (authorizedClient == null || authorizedClient.getAccessToken() == null)
{
throw new CredentialsVerificationException("Resource Owner Password Credentials is not supported by the Authorization Server.");
}
}
@Override
public Optional<String> extractUsernameFromToken(String token)
{
final Jwt validToken;
try
{
validToken = jwtDecoder.decode(requireNonNull(token));
}
catch (RuntimeException e)
{
throw new TokenException("Failed to decode token. " + e.getMessage(), e);
}
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Bearer token outcome: " + validToken);
}
return Optional.ofNullable(validToken)
.map(Jwt::getClaims)
.map(c -> c.get("preferred_username"))
.filter(String.class::isInstance)
.map(String.class::cast);
}
private OAuth2AuthorizeRequest createPasswordCredentialsRequest(String userName, String password)
{
return OAuth2AuthorizeRequest
.withClientRegistrationId(CLIENT_REGISTRATION_ID)
.principal(userName)
.attribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, userName)
.attribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password)
.build();
}
return value != null && !value.isBlank();
}
}

View File

@@ -34,7 +34,7 @@ import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.TokenException;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
import org.alfresco.service.cmr.security.PersonService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -50,6 +50,7 @@ import org.springframework.security.oauth2.server.resource.web.BearerTokenResolv
public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, ActivateableBean
{
private static final Log LOGGER = LogFactory.getLog(IdentityServiceRemoteUserMapper.class);
static final String USERNAME_CLAIM = "preferred_username";
/** Is the mapper enabled */
private boolean isEnabled;
@@ -131,7 +132,7 @@ public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, Activa
return normalizedUserId;
}
}
catch (TokenException e)
catch (IdentityServiceFacadeException e)
{
if (!isValidationFailureSilent)
{
@@ -160,7 +161,7 @@ public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, Activa
* Extracts the user name from the JWT in the given request.
*
* @param request The request containing the JWT
* @return The user name or null if it can not be determined
* @return The username or null if it can not be determined
*/
private String extractUserFromHeader(HttpServletRequest request)
{
@@ -179,7 +180,11 @@ public class IdentityServiceRemoteUserMapper implements RemoteUserMapper, Activa
}
final Optional<String> possibleUsername = Optional.ofNullable(bearerToken)
.flatMap(identityServiceFacade::extractUsernameFromToken);
.map(identityServiceFacade::decodeToken)
.map(t -> t.getClaim(USERNAME_CLAIM))
.filter(String.class::isInstance)
.map(String.class::cast);
if (possibleUsername.isEmpty())
{
LOGGER.debug("User could not be authenticated by IdentityServiceRemoteUserMapper.");

View File

@@ -0,0 +1,255 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2023 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;
import static java.util.Objects.requireNonNull;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.web.client.RestOperations;
class SpringBasedIdentityServiceFacade implements IdentityServiceFacade
{
private static final Log LOGGER = LogFactory.getLog(SpringBasedIdentityServiceFacade.class);
private static final Instant SOME_INSIGNIFICANT_DATE_IN_THE_PAST = Instant.MIN.plusSeconds(12345);
private final Map<AuthorizationGrantType, OAuth2AccessTokenResponseClient> clients;
private final ClientRegistration clientRegistration;
private final JwtDecoder jwtDecoder;
SpringBasedIdentityServiceFacade(RestOperations restOperations, ClientRegistration clientRegistration, JwtDecoder jwtDecoder)
{
requireNonNull(restOperations);
this.clientRegistration = requireNonNull(clientRegistration);
this.jwtDecoder = requireNonNull(jwtDecoder);
this.clients = Map.of(
AuthorizationGrantType.AUTHORIZATION_CODE, createAuthorizationCodeClient(restOperations),
AuthorizationGrantType.REFRESH_TOKEN, createRefreshTokenClient(restOperations),
AuthorizationGrantType.PASSWORD, createPasswordClient(restOperations));
}
@Override
public AccessTokenAuthorization authorize(AuthorizationGrant authorizationGrant)
{
final AbstractOAuth2AuthorizationGrantRequest request = createRequest(authorizationGrant);
final OAuth2AccessTokenResponseClient client = getClient(request);
final OAuth2AccessTokenResponse response;
try
{
response = client.getTokenResponse(request);
}
catch (OAuth2AuthorizationException e)
{
LOGGER.debug("Failed to authorize against Authorization Server. Reason: " + e.getError() + ".");
throw new AuthorizationException("Failed to obtain access token. " + e.getError(), e);
}
catch (RuntimeException e)
{
LOGGER.warn("Failed to authorize against Authorization Server. Reason: " + e.getMessage());
throw new AuthorizationException("Failed to obtain access token.", e);
}
return new SpringAccessTokenAuthorization(response);
}
@Override
public DecodedAccessToken decodeToken(String token)
{
final Jwt validToken;
try
{
validToken = jwtDecoder.decode(token);
}
catch (RuntimeException e)
{
throw new TokenDecodingException("Failed to decode token. " + e.getMessage(), e);
}
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Bearer token outcome: " + validToken.getClaims());
}
return new SpringDecodedAccessToken(validToken);
}
private AbstractOAuth2AuthorizationGrantRequest createRequest(AuthorizationGrant grant)
{
if (grant.isPassword())
{
return new OAuth2PasswordGrantRequest(clientRegistration, grant.getUsername(), grant.getPassword());
}
if (grant.isRefreshToken())
{
final OAuth2AccessToken expiredAccessToken = new OAuth2AccessToken(
TokenType.BEARER,
"JUST_FOR_FULFILLING_THE_SPRING_API",
SOME_INSIGNIFICANT_DATE_IN_THE_PAST,
SOME_INSIGNIFICANT_DATE_IN_THE_PAST.plusSeconds(1));
final OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(grant.getRefreshToken(), null);
return new OAuth2RefreshTokenGrantRequest(clientRegistration, expiredAccessToken, refreshToken, clientRegistration.getScopes());
}
if (grant.isAuthorizationCode())
{
final OAuth2AuthorizationExchange authzExchange = new OAuth2AuthorizationExchange(
OAuth2AuthorizationRequest.authorizationCode()
.clientId(clientRegistration.getClientId())
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.redirectUri(grant.getRedirectUri())
.scopes(clientRegistration.getScopes())
.build(),
OAuth2AuthorizationResponse.success(grant.getAuthorizationCode())
.redirectUri(grant.getRedirectUri())
.build()
);
return new OAuth2AuthorizationCodeGrantRequest(clientRegistration, authzExchange);
}
throw new UnsupportedOperationException("Unsupported grant type.");
}
private OAuth2AccessTokenResponseClient getClient(AbstractOAuth2AuthorizationGrantRequest request)
{
final AuthorizationGrantType grantType = request.getGrantType();
final OAuth2AccessTokenResponseClient client = clients.get(grantType);
if (client == null)
{
throw new UnsupportedOperationException("Unsupported grant type `" + grantType + "`.");
}
return client;
}
private static OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> createAuthorizationCodeClient(RestOperations rest)
{
final DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
client.setRestOperations(rest);
return client;
}
private static OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> createRefreshTokenClient(RestOperations rest)
{
final DefaultRefreshTokenTokenResponseClient client = new DefaultRefreshTokenTokenResponseClient();
client.setRestOperations(rest);
return client;
}
private static OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> createPasswordClient(RestOperations rest)
{
final DefaultPasswordTokenResponseClient client = new DefaultPasswordTokenResponseClient();
client.setRestOperations(rest);
return client;
}
private static class SpringAccessTokenAuthorization implements AccessTokenAuthorization
{
private final OAuth2AccessTokenResponse tokenResponse;
private SpringAccessTokenAuthorization(OAuth2AccessTokenResponse tokenResponse)
{
this.tokenResponse = requireNonNull(tokenResponse);
}
@Override
public AccessToken getAccessToken()
{
return new SpringAccessToken(tokenResponse.getAccessToken());
}
@Override
public String getRefreshTokenValue()
{
return Optional.of(tokenResponse)
.map(OAuth2AccessTokenResponse::getRefreshToken)
.map(AbstractOAuth2Token::getTokenValue)
.orElse(null);
}
}
private static class SpringAccessToken implements AccessToken
{
private final AbstractOAuth2Token token;
private SpringAccessToken(AbstractOAuth2Token token)
{
this.token = requireNonNull(token);
}
@Override
public String getTokenValue()
{
return token.getTokenValue();
}
@Override
public Instant getExpiresAt()
{
return token.getExpiresAt();
}
}
private static class SpringDecodedAccessToken extends SpringAccessToken implements DecodedAccessToken
{
private final Jwt jwt;
private SpringDecodedAccessToken(Jwt jwt)
{
super(jwt);
this.jwt = jwt;
}
@Override
public Object getClaim(String claim)
{
return jwt.getClaim(claim);
}
}
}

View File

@@ -48,6 +48,8 @@ import org.alfresco.util.Pair;
@AlfrescoPublicApi
public interface TaggingService
{
NodeRef TAG_ROOT_NODE_REF = new NodeRef("workspace://SpacesStore/tag:tag-root");
/**
* Indicates whether the tag already exists
*

View File

@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited
* Copyright (C) 2005 - 2023 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,6 +28,8 @@ package org.alfresco.transform.registry;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.httpclient.HttpClient4Factory;
import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.repo.content.transform.LocalPassThroughTransform;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.transform.config.TransformConfig;
@@ -39,7 +41,6 @@ import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
@@ -67,8 +68,11 @@ public class CombinedConfig extends CombinedTransformConfig
private ConfigFileFinder configFileFinder;
private int tEngineCount;
public CombinedConfig(Log log, AbstractTransformRegistry registry)
private final HttpClientConfig httpClientConfig;
public CombinedConfig(Log log, AbstractTransformRegistry registry, HttpClientConfig httpClientConfig)
{
this.httpClientConfig = httpClientConfig;
this.log = log;
configFileFinder = new ConfigFileFinder(jsonObjectMapper)
@@ -87,88 +91,84 @@ public class CombinedConfig extends CombinedTransformConfig
return configFileFinder.readFiles(path, log);
}
public boolean addRemoteConfig(List<String> urls, String remoteType)
public boolean addRemoteConfig(List<String> urls, String remoteType) throws IOException
{
boolean successReadingConfig = true;
for (String url : urls)
try(CloseableHttpClient httpclient = HttpClient4Factory.createHttpClient(httpClientConfig))
{
if (addRemoteConfig(url, remoteType))
boolean successReadingConfig = true;
for (String url : urls)
{
tEngineCount++ ;
}
else
{
successReadingConfig = false;
if (addRemoteConfig(httpclient, url, remoteType))
{
tEngineCount++;
} else
{
successReadingConfig = false;
}
}
return successReadingConfig;
}
return successReadingConfig;
}
private boolean addRemoteConfig(String baseUrl, String remoteType)
private boolean addRemoteConfig(CloseableHttpClient httpclient, String baseUrl, String remoteType)
{
String url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + ENDPOINT_TRANSFORM_CONFIG_LATEST;
HttpGet httpGet = new HttpGet(url);
boolean successReadingConfig = true;
try
{
try (CloseableHttpClient httpclient = HttpClients.createDefault())
try (CloseableHttpResponse response = execute(httpclient, httpGet))
{
try (CloseableHttpResponse response = execute(httpclient, httpGet))
StatusLine statusLine = response.getStatusLine();
if (statusLine == null)
{
StatusLine statusLine = response.getStatusLine();
if (statusLine == null)
throw new AlfrescoRuntimeException(remoteType+" on " + url+" returned no status ");
}
HttpEntity resEntity = response.getEntity();
if (resEntity != null)
{
int statusCode = statusLine.getStatusCode();
if (statusCode == 200)
{
throw new AlfrescoRuntimeException(remoteType+" on " + url+" returned no status ");
}
HttpEntity resEntity = response.getEntity();
if (resEntity != null)
{
int statusCode = statusLine.getStatusCode();
if (statusCode == 200)
try
{
try
String content = getContent(resEntity);
try (StringReader reader = new StringReader(content))
{
String content = getContent(resEntity);
try (StringReader reader = new StringReader(content))
int transformCount = transformerCount();
configFileFinder.readFile(reader, remoteType+" on "+baseUrl, "json", baseUrl, log);
if (transformCount == transformerCount())
{
int transformCount = transformerCount();
configFileFinder.readFile(reader, remoteType+" on "+baseUrl, "json", baseUrl, log);
if (transformCount == transformerCount())
{
successReadingConfig = false;
}
successReadingConfig = false;
}
}
EntityUtils.consume(resEntity);
}
catch (IOException e)
{
throw new AlfrescoRuntimeException("Failed to read the returned content from "+
remoteType+" on " + url, e);
}
EntityUtils.consume(resEntity);
}
else
catch (IOException e)
{
String message = getErrorMessage(resEntity);
throw new AlfrescoRuntimeException(remoteType+" on " + url+" returned a " + statusCode +
" status " + message);
throw new AlfrescoRuntimeException("Failed to read the returned content from "+
remoteType+" on " + url, e);
}
}
else
{
throw new AlfrescoRuntimeException(remoteType+" on " + url+" did not return an entity " + url);
String message = getErrorMessage(resEntity);
throw new AlfrescoRuntimeException(remoteType+" on " + url+" returned a " + statusCode +
" status " + message);
}
}
catch (IOException e)
else
{
throw new AlfrescoRuntimeException("Failed to connect or to read the response from "+remoteType+
" on " + url, e);
throw new AlfrescoRuntimeException(remoteType+" on " + url+" did not return an entity " + url);
}
}
catch (IOException e)
{
throw new AlfrescoRuntimeException(remoteType+" on " + url+" failed to create an HttpClient", e);
throw new AlfrescoRuntimeException("Failed to connect or to read the response from "+remoteType+
" on " + url, e);
}
}
catch (AlfrescoRuntimeException e)
{

View File

@@ -41,6 +41,12 @@ public class UrlUtil
public static final Pattern PATTERN = Pattern.compile("\\$\\{shareUrl\\}");
// ${alfrescoUrl} placeholder
public static final Pattern REPO_PATTERN = Pattern.compile("\\$\\{alfrescoUrl\\}");
public static final Pattern REPOBASE_PATTERN = Pattern.compile("\\$\\{repoBaseUrl\\}");
public static final Pattern WORKSPACE_PATTERN = Pattern.compile("\\$\\{workspaceUrl\\}");
/**
* Builds up the Url to Alfresco based on the settings in the
* {@link SysAdminParams}.
@@ -146,4 +152,68 @@ public class UrlUtil
url.append(context);
return url.toString();
}
/**
* Builds up the Url to Adw based on the settings in the
* {@link SysAdminParams}.
* @return Adw Url such as https://col.ab.or.ate/#/
* or http://localhost:8081/#/
*/
public static String getWorkspaceUrl(SysAdminParams sysAdminParams)
{
return buildWorkspaceUrl(
sysAdminParams.getAlfrescoProtocol(),
sysAdminParams.getAlfrescoHost(),
sysAdminParams.getAlfrescoPort());
}
protected static String buildWorkspaceUrl(String workSpaceProtocol, String workspaceHost, int workspacePort) {
StringBuilder workspaceUrl = new StringBuilder();
workspaceUrl.append(workSpaceProtocol);
workspaceUrl.append("://");
workspaceUrl.append(workspaceHost);
if ("http".equals(workSpaceProtocol) && workspacePort == 80)
{
// Not needed
}
else if ("https".equals(workSpaceProtocol) && workspacePort == 443)
{
// Not needed
}
else
{
workspaceUrl.append(':');
workspaceUrl.append(workspacePort);
}
return workspaceUrl.toString();
}
/**
* Replaces the repo base url placeholder, namely {@literal ${repoBaseUrl}}, with <b>workspace</b> url.
*
* @param value the string value which contains the repoBase url placeholder
* @param sysAdminParams the {@code SysAdminParams} object
* @return if the given {@code value} contains share url placeholder,
* the placeholder is replaced with share url; otherwise, the given {@code value} is simply returned
*/
public static String replaceRepoBaseUrlPlaceholder(String value, SysAdminParams sysAdminParams)
{
if (value != null)
{
return REPOBASE_PATTERN.matcher(value).replaceAll(getWorkspaceUrl(sysAdminParams));
}
return value;
}
public static String replaceWorkSpaceUrlPlaceholder(String pageUrl,String workspaceUrl)
{
if (pageUrl != null)
{
return WORKSPACE_PATTERN.matcher(pageUrl).replaceAll(workspaceUrl);
}
return pageUrl;
}
}

View File

@@ -1,144 +0,0 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.util.remote;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.InetAddress;
import org.alfresco.util.remote.server.RemoteInputStreamServer;
import org.alfresco.util.remote.server.RmiRemoteInputStreamServer;
/**
* The data consuming side of the remote connection that the <code>InputStream</code> spans.
*
* @author <a href="mailto:Michael.Shavnev@effective-soft.com">Michael Shavnev</a>
* @since Alfresco 2.2
*/
public class RemotableInputStream extends InputStream implements Serializable
{
private static final long serialVersionUID = 2434858590717000057L;
private int port;
private String host;
private String name;
transient private RemoteInputStreamServer inputStreamServer;
public RemotableInputStream(String host, int port, InputStream inputStream)
{
this.host = host;
this.port = port;
this.inputStreamServer = new RmiRemoteInputStreamServer(inputStream);
}
public void close() throws IOException
{
inputStreamServer.close();
}
public int read() throws IOException
{
return inputStreamServer.read();
}
public int read(byte[] bytes) throws IOException
{
return inputStreamServer.read(bytes);
}
public int read(byte[] bytes, int off, int len) throws IOException
{
return inputStreamServer.read(bytes, off, len);
}
public long skip(long n) throws IOException
{
return inputStreamServer.skip(n);
}
public int available() throws IOException
{
return inputStreamServer.available();
}
public void mark(int readlimit)
{
inputStreamServer.mark(readlimit);
}
public boolean markSupported()
{
return inputStreamServer.markSupported();
}
public void reset() throws IOException
{
inputStreamServer.reset();
}
private void writeObject(ObjectOutputStream out) throws IOException
{
name = inputStreamServer.start(host, port);
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
{
in.defaultReadObject();
inputStreamServer = (RemoteInputStreamServer) RmiRemoteInputStreamServer.obtain(host, port, name);
}
public static void main(String[] args) throws Exception
{
RemotableInputStream remotableInputStream = new RemotableInputStream(InetAddress.getLocalHost().getHostName(), 7777, new ByteArrayInputStream("test".getBytes()));
for (int b = -1; (b = remotableInputStream.read()) != -1;)
{
System.out.println((char) b);
}
remotableInputStream = new RemotableInputStream(InetAddress.getLocalHost().getHostName(), 7777, new ByteArrayInputStream("test".getBytes()));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(remotableInputStream);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
remotableInputStream = (RemotableInputStream) ois.readObject();
for (int b = -1; (b = remotableInputStream.read()) != -1;)
{
System.out.println((char) b);
}
remotableInputStream.close();
}
}

View File

@@ -1,90 +0,0 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.util.remote.server;
import java.io.IOException;
import java.io.InputStream;
/**
* The data producing side of the remote connection that the <code>InputStream</code> spans.
*
* @author <a href="mailto:Michael.Shavnev@effective-soft.com">Michael Shavnev</a>
* @since Alfresco 2.2
*/
public abstract class AbstractRemoteInputStreamServer implements RemoteInputStreamServer
{
protected InputStream inputStream;
protected AbstractRemoteInputStreamServer(InputStream inputStream)
{
this.inputStream = inputStream;
}
public int read() throws IOException
{
return inputStream.read();
}
public int read(byte[] bytes) throws IOException
{
return inputStream.read(bytes);
}
public int read(byte[] bytes, int off, int len) throws IOException
{
return inputStream.read(bytes, off, len);
}
public long skip(long n) throws IOException
{
return inputStream.skip(n);
}
public int available() throws IOException
{
return inputStream.available();
}
public void mark(int readlimit)
{
inputStream.mark(readlimit);
}
public boolean markSupported()
{
return inputStream.markSupported();
}
public void reset() throws IOException
{
inputStream.reset();
}
public void close() throws IOException
{
inputStream.close();
}
}

View File

@@ -1,58 +0,0 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.util.remote.server;
import java.io.IOException;
import java.rmi.RemoteException;
/**
* Interface for remote input stream support.
*
* @author <a href="mailto:Michael.Shavnev@effective-soft.com">Michael Shavnev</a>
* @since Alfresco 2.2
*/
public interface RemoteInputStreamServer
{
public String start(String host, int port) throws RemoteException;
public int read() throws IOException;
public int read(byte[] bytes) throws IOException;
public int read(byte[] bytes, int off, int len) throws IOException;
public long skip(long n) throws IOException;
public int available() throws IOException;
public void mark(int readlimit);
public boolean markSupported();
public void reset() throws IOException;
public void close() throws IOException;
}

View File

@@ -1,108 +0,0 @@
/*
* #%L
* Alfresco Repository
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.util.remote.server;
import java.io.IOException;
import java.io.InputStream;
import java.rmi.RemoteException;
import java.util.UUID;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import org.springframework.remoting.rmi.RmiServiceExporter;
/**
* Concrete implementation of a remoting InputStream based on RMI.
*
* @author <a href="mailto:Michael.Shavnev@effective-soft.com">Michael Shavnev</a>
* @since Alfresco 2.2
*/
public class RmiRemoteInputStreamServer extends AbstractRemoteInputStreamServer
{
private RmiServiceExporter rmiServiceExporter;
public RmiRemoteInputStreamServer(InputStream inputStream)
{
super(inputStream);
}
public String start(String host, int port) throws RemoteException
{
String name = inputStream.getClass().getName() + UUID.randomUUID();
rmiServiceExporter = new RmiServiceExporter();
rmiServiceExporter.setServiceName(name);
rmiServiceExporter.setRegistryPort(port);
rmiServiceExporter.setRegistryHost(host);
rmiServiceExporter.setServiceInterface(RemoteInputStreamServer.class);
rmiServiceExporter.setService(this);
rmiServiceExporter.afterPropertiesSet();
return name;
}
/**
* Closes the stream and the RMI connection to the peer.
*/
public void close() throws IOException
{
try
{
inputStream.close();
}
finally
{
if (rmiServiceExporter != null)
{
try
{
rmiServiceExporter.destroy();
}
catch (Throwable e)
{
throw new IOException(e.getMessage());
}
}
}
}
/**
* Utility method to lookup a remote stream peer over RMI.
*/
public static RemoteInputStreamServer obtain(String host, int port, String name) throws RemoteException
{
RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
rmiProxyFactoryBean.setServiceUrl("rmi://" + host + ":" + port + "/" + name);
rmiProxyFactoryBean.setServiceInterface(RemoteInputStreamServer.class);
rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);
try
{
rmiProxyFactoryBean.afterPropertiesSet();
}
catch (Exception e)
{
throw new RemoteException("Error create rmi proxy");
}
return (RemoteInputStreamServer) rmiProxyFactoryBean.getObject();
}
}

View File

@@ -20,6 +20,16 @@ repo.client-app.share.resetPasswordPageUrl=${shareUrl}/page/reset-password
repo.client-app.share.confirmResetPasswordTemplatePath=
### Digital workspace template configurations
repo.client-app.workspace.inviteModeratedTemplatePath=
repo.client-app.workspace.workspaceUrl=workspace
repo.client-app.workspace.templateAssetsUrl=${alfrescoUrl}/images
#repo.client-app.workspace.inviteModeratedTemplatePath=
repo.client-app.workspace.workspaceUrl=${repoBaseUrl}/workspace
#repo.client-app.workspace.templateAssetsUrl=${workspaceUrl}/images
repo.client-app.workspace.templateAssetsUrl=alfresco/templates/reset-password-email-templates/images
# reset password request email template path
repo.client-app.workspace.requestResetPasswordTemplatePath=alfresco/templates/reset-password-email-templates/forgot-password-email-template.ftl
# reset password UI page url
repo.client-app.workspace.resetPasswordPageUrl=${workspaceUrl}/reset-password/
# reset password confirmation email template path
repo.client-app.workspace.confirmResetPasswordTemplatePath=

View File

@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- Remote FileFolderService exposure -->
<bean id="fileFolderRemoteServer" class="org.alfresco.repo.remote.FileFolderRemoteServer">
<property name="transactionService">
<ref bean="TransactionService"/>
</property>
<property name="fileFolderService">
<ref bean="FileFolderService"/>
</property>
<property name="authenticationService">
<ref bean="AuthenticationService"/>
</property>
</bean>
<bean id="fileFolderRemoteRMI" class="org.springframework.remoting.rmi.RmiServiceExporter" parent="baseServiceExporter">
<property name="service">
<ref bean="fileFolderRemoteServer"/>
</property>
<property name="serviceInterface">
<value>org.alfresco.service.cmr.remote.FileFolderRemote</value>
</property>
<property name="serviceName">
<value>org.alfresco.FileFolderRemote</value>
</property>
<property name="registryPort">
<value>${alfresco.rmi.services.port}</value>
</property>
</bean>
<!-- Remote Loader exposure -->
<bean id="loaderRemoteServer" class="org.alfresco.repo.remote.LoaderRemoteServer">
<property name="transactionService">
<ref bean="TransactionService"/>
</property>
<property name="authenticationService">
<ref bean="AuthenticationService"/>
</property>
<property name="nodeService">
<ref bean="NodeService"/>
</property>
<property name="fileFolderService">
<ref bean="FileFolderService"/>
</property>
<property name="checkOutCheckInService">
<ref bean="CheckoutCheckinService"/>
</property>
<property name="fileFolderRemote">
<ref bean="fileFolderRemoteServer"/>
</property>
</bean>
<bean id="loaderRemoteServerRMI" class="org.springframework.remoting.rmi.RmiServiceExporter" parent="baseServiceExporter">
<property name="service">
<ref bean="loaderRemoteServer"/>
</property>
<property name="serviceInterface">
<value>org.alfresco.service.cmr.remote.LoaderRemote</value>
</property>
<property name="serviceName">
<value>org.alfresco.LoaderRemote</value>
</property>
<property name="registryPort">
<value>${alfresco.rmi.services.port}</value>
</property>
</bean>
</beans>

View File

@@ -107,6 +107,13 @@
<!-- Replaced in the enterprise edition -->
<bean id="remoteTransformServiceRegistry" class="org.alfresco.repo.content.transform.DummyTransformServiceRegistry" />
<bean id="httpClientConfigTransform" class="org.alfresco.httpclient.HttpClientConfig" init-method="init" >
<property name="sslEncryptionParameters" ref="sslEncryptionParameters" />
<property name="keyResourceLoader" ref="springKeyResourceLoader" />
<property name="properties" ref="global-properties" />
<property name="serviceName" value="transform" />
</bean>
<bean id="localTransformServiceRegistry" class="org.alfresco.repo.content.transform.LocalTransformServiceRegistry" >
<property name="jsonObjectMapper" ref="localTransformServiceRegistryJsonObjectMapper" />
<property name="pipelineConfigDir" value="${local.transform.pipeline.config.dir}" />
@@ -118,7 +125,8 @@
<property name="mimetypeService" ref="MimetypeService" />
<property name="strictMimeTypeCheck" value="${transformer.strict.mimetype.check}"/>
<property name="retryTransformOnDifferentMimeType" value="${content.transformer.retryOn.different.mimetype}"/>
<property name="shutdownIndicator" ref="shutdownIndicator"></property>
<property name="shutdownIndicator" ref="shutdownIndicator" />
<property name="httpClientConfig" ref="httpClientConfigTransform" />
</bean>
<bean id="localTransformServiceRegistryJsonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />

View File

@@ -749,6 +749,15 @@ encryption.ssl.truststore.type=JCEKS
# configuration via metadata is deprecated
encryption.ssl.truststore.keyMetaData.location=
## HttpClient config for Transform Service
#enable mtls in HttpClientFactory for Transform Service.
httpclient.config.transform.mTLSEnabled=false
httpclient.config.transform.maxTotalConnections=40
httpclient.config.transform.maxHostConnections=40
httpclient.config.transform.socketTimeout=5000
httpclient.config.transform.connectionRequestTimeout=5000
httpclient.config.transform.connectionTimeout=5000
# Re-encryptor properties
encryption.reencryptor.chunkSize=100
encryption.reencryptor.numThreads=2

View File

@@ -86,59 +86,17 @@
</bean>
<bean name="identityServiceConfig" class="org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig">
<property name="globalProperties">
<ref bean="global-properties" />
</property>
<property name="realm">
<value>${identity-service.realm}</value>
</property>
<property name="realmKey">
<value>${identity-service.realm-public-key:#{null}}</value>
</property>
<property name="authServerUrl">
<value>${identity-service.auth-server-url}</value>
</property>
<property name="sslRequired">
<value>${identity-service.ssl-required:external}</value>
</property>
<property name="confidentialPort">
<value>${identity-service.confidential-port:0}</value>
</property>
<property name="resource">
<value>${identity-service.resource}</value>
</property>
<property name="useResourceRoleMappings">
<value>${identity-service.use-resource-role-mappings:false}</value>
</property>
<property name="cors">
<value>${identity-service.enable-cors:false}</value>
</property>
<property name="corsMaxAge">
<value>${identity-service.cors-max-age:-1}</value>
</property>
<property name="corsAllowedHeaders">
<value>${identity-service.cors-allowed-headers:#{null}}</value>
</property>
<property name="corsAllowedMethods">
<value>${identity-service.cors-allowed-methods:#{null}}</value>
</property>
<property name="corsExposedHeaders">
<value>${identity-service.cors-exposed-headers:#{null}}</value>
</property>
<property name="exposeToken">
<value>${identity-service.expose-token:false}</value>
</property>
<property name="bearerOnly">
<value>${identity-service.bearer-only:false}</value>
</property>
<property name="autodetectBearerOnly">
<value>${identity-service.autodetect-bearer-only:false}</value>
</property>
<property name="enableBasicAuth">
<value>${identity-service.enable-basic-auth:false}</value>
</property>
<property name="publicClient">
<value>${identity-service.public-client:false}</value>
<property name="clientSecret">
<value>${identity-service.credentials.secret:#{null}}</value>
</property>
<property name="allowAnyHostname">
<value>${identity-service.allow-any-hostname:false}</value>
@@ -164,45 +122,21 @@
<property name="connectionPoolSize">
<value>${identity-service.connection-pool-size:20}</value>
</property>
<property name="alwaysRefreshToken">
<value>${identity-service.always-refresh-token:false}</value>
</property>
<property name="registerNodeAtStartup">
<value>${identity-service.register-node-at-startup:false}</value>
</property>
<property name="registerNodePeriod">
<value>${identity-service.register-node-period:-1}</value>
</property>
<property name="tokenStore">
<value>${identity-service.token-store:#{null}}</value>
</property>
<property name="principalAttribute">
<value>${identity-service.principal-attribute:#{null}}</value>
</property>
<property name="turnOffChangeSessionIdOnLogin">
<value>${identity-service.turn-off-change-session-id-on-login:false}</value>
</property>
<property name="tokenMinimumTimeToLive">
<value>${identity-service.token-minimum-time-to-live:0}</value>
</property>
<property name="minTimeBetweenJwksRequests">
<value>${identity-service.min-time-between-jwks-requests:10}</value>
</property>
<property name="publicKeyCacheTtl">
<value>${identity-service.public-key-cache-ttl:86400}</value>
</property>
<property name="pkce">
<value>${identity-service.enable-pkce:false}</value>
</property>
<property name="ignoreOAuthQueryParameter">
<value>${identity-service.ignore-oauth-query-parameter:false}</value>
</property>
<property name="clientConnectionTimeout">
<value>${identity-service.client-connection-timeout:2000}</value>
</property>
<property name="clientSocketTimeout">
<value>${identity-service.client-socket-timeout:2000}</value>
</property>
<property name="realmKey">
<value>${identity-service.realm-public-key:#{null}}</value>
</property>
<property name="publicKeyCacheTtl">
<value>${identity-service.public-key-cache-ttl:86400}</value>
</property>
<property name="publicClient">
<value>${identity-service.public-client:false}</value>
</property>
</bean>
<!-- Enable control over mapping between request and user ID -->

View File

@@ -2,12 +2,12 @@ identity-service.authentication.enabled=true
identity-service.authentication.validation.failure.silent=true
identity-service.authentication.defaultAdministratorUserNames=admin
identity-service.authentication.allowGuestLogin=true
# The keycloak client required to perform username/password authentication will not be created if false
# The Identity Service client required to perform username/password authentication will not be created if false
identity-service.authentication.enable-username-password-authentication=true
# Identity Service configuration
identity-service.auth-server-url=http://localhost:8180/auth
identity-service.realm=alfresco
identity-service.ssl-required=none
identity-service.resource=alfresco
identity-service.credentials.secret=
identity-service.public-client=true

View File

@@ -37,13 +37,14 @@
<bean id="taggingService" class="org.alfresco.repo.tagging.TaggingServiceImpl" init-method="init">
<property name="nodeService" ref="NodeService"/>
<property name="nodeServiceInternal" ref="nodeService"/>
<property name="categoryService" ref="CategoryService"/>
<property name="searchService" ref="SearchService"/>
<property name="actionService" ref="ActionService"/>
<property name="contentService" ref="ContentService"/>
<property name="namespaceService" ref="NamespaceService"/>
<property name="policyComponent" ref="policyComponent"/>
<property name="auditComponent" ref="auditComponent"/>
<property name="categoryService" ref="CategoryService"/>
<property name="searchService" ref="SearchService"/>
<property name="actionService" ref="ActionService"/>
<property name="contentService" ref="ContentService"/>
<property name="namespaceService" ref="NamespaceService"/>
<property name="policyComponent" ref="policyComponent"/>
<property name="auditComponent" ref="auditComponent"/>
<property name="eventGenerator" ref="eventGeneratorV2"/>
</bean>
<bean id="update-tagscope" class="org.alfresco.repo.tagging.UpdateTagScopesActionExecuter" parent="action-executer">

View File

@@ -0,0 +1,428 @@
<html>
<head>
<style type="text/css">
td {
font-family: 'Helvetica Neue', Arial, sans-serif;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% ! important;
height: 100% !important;
color: #727174;
font-weight: 400;
font-size: 18px;
margin: 0;
}
h1 {
margin: 10px 0;
}
h2 {
color: #727174;
font-weight: 600;
font-size: 22px;
}
a {
color: #0c79bf;
text-decoration: underline;
}
a.linkone {
color: #0c79bf;
text-decoration: underline;
}
.appleLinks a {
color: #0c79bf;
}
.appleLinksWhite a {
color: #0c79bf;
}
.force-full-width {
width: 100% !important;
}
.body-padding {
padding: 0 75px
}
.force-width-80 {
width: 80% !important;
}
p {
Margin-bottom: 1em;
}
.button {
text-align: center;
font-size: 18px;
font-family: sans-serif;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
}
.button a {
color: #ffffff;
text-decoration: none;
}
img {
max-width: 600px;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
a img {
border: none;
}
table {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
#outlook a {
padding: 0;
}
.ReadMsgBody {
width: 100%;
}
.ExternalClass {
width: 100%;
}
.backgroundTable {
margin: 0 auto;
padding: 0;
width: 100% !important;
}
table td {
border-collapse: collapse;
}
.ExternalClass * {
line-height: 115%;
}
.ExternalClass {
vertical-align: middle
}
/*]]>*/
</style>
<style type="text/css" media="screen">
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { {
@-ms-viewport {
width: 320px;
}
@viewport {
width: 320px;
}
}
</style>
<style type="text/css" media="screen">
@media screen {
* {
font-family: 'Helvetica Neue', 'Arial', 'sans-serif' !important;
}
.w280 {
width: 280px !important;
}
}
</style>
<style type="text/css" media="only screen and (max-width: 480px)">
@media only screen and (max-width: 480px) {
.full {
display: block;
width: 100%;
}
table[class*="w320"] {
width: 320px !important;
}
td[class*="w320"] {
width: 280px !important;
padding-left: 20px !important;
padding-right: 20px !important;
}
img[class*="w320"] {
width: 250px !important;
height: 67px !important;
}
td[class*="mobile-spacing"] {
padding-top: 10px !important;
padding-bottom: 10px !important;
}
*[class*="mobile-hide"] {
display: none !important;
}
*[class*="mobile-br"] {
font-size: 8px !important;
}
*[class*="mobile-brh"] {
font-size: 1px !important;
}
td[class*="mobile-w20"] {
width: 20px !important;
}
img[class*="mobile-w20"] {
width: 20px !important;
}
td[class*="mobile-center"] {
text-align: center !important;
}
table[class*="w100p"] {
width: 100% !important;
}
td[class*="activate-now"] {
padding-right: 0 !important;
padding-top: 20px !important;
}
td[class*="mobile-resize"] {
font-size: 22px !important;
padding-left: 15px !important;
}
}
</style>
</head>
<body offset="0" class="body" style="padding:0; margin:0; display:block; background:#f3f4f4; -webkit-text-size-adjust:none; " bgcolor="#EEEEEE">
<table align="center" cellpadding="0" cellspacing="0" width="100%" height="100%">
<tr>
<td align="center" valign="top" style="background-color:#f3f4f4; " width="100%">
<table cellspacing="0" cellpadding="0" width="600" class="w320">
<tr>
<td align="center" valign="top">
<!--Snippet Block-->
<table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
<tr>
<td>
<table border="0" cellspacing="0" cellpadding="10" width="100%" align="center">
<tbody>
<tr>
<td align="left">
<table align="center" width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="right">
<div style="display:none;font-size:1px;color:#333333;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">
-
</div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<!--Header Block-->
<table bgcolor="#FFFFFF" border="0" cellspacing="0" cellpadding="0" align="center" width="100%"
style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; border-bottom:solid 1px #dedee4; border-top:solid 1px #dedee4; width:100%!important; min-width:100%;">
<tr style="width:100%;">
<td>
<table border="0" cellspacing="0" cellpadding="10" align="center" width="100%">
<tr>
<td align="left" valign="middle"><a style="color: #ffffff; text-decoration: none;" href=
"http://go.alfresco.com/a0100K050L0000KZjI2d00U" target="_blank"
><span style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; color: #727174;"><img
style="border:none; display:block; border-collapse:collapse; outline:none; text-decoration:none;"
src="${template_assets_url}/hyland_logo.png" border="0"
alt="Alfresco" width="122" height="38"></span></a></td>
</tr>
</table>
</td>
<td align="right" bgcolor="#FFFFFF">
<table border="0" cellspacing="0" cellpadding="10" align="center" width="100%">
<tr>
<td align="right" valign="middle"><span
style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; font-weight:200; color: #727174;">Digital Workspace</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Body Block 1-->
<!--Banner Block-->
<table cellspacing="0" cellpadding="0" width="100%" style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; ">
<tr>
<td style="border-collapse:collapse; ">
<table cellspacing="0" cellpadding="0" width="100%" style="background-color:#0c79bf; border:none; ">
<tr>
<td style="border-collapse:collapse; ">
<div class="mktEditable" id="Banner Image 1"><span
style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; color: #ffffff;"><img
src="${template_assets_url}/adw_logo.png" alt="Alfresco Products" style="width:100%; border:none;
display:block; border-collapse:collapse; outline:none; text-decoration:none;"></span></div>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Copy-->
<table cellspacing="0" cellpadding="0" align="center" bgcolor="#FFFFFF"
style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; border-bottom:solid 1px #dedee4; " width="100%">
<tr align="center">
<td style="background-color:#ffffff; " align="center">
<table class="force-width-80" cellspacing="0" cellpadding="12" align="center" bgcolor="#FFFFFF" width="80%">
<tbody>
<tr>
<td align="left">
<!--Headline Text-->
<div class="mktEditable" id="Headline Text 1" align="left" style="vertical-align:middle; "><span
style="color: #727174; margin: 0px; font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 22px; font-weight: 600; text-align: left; text-decoration: none; vertical-align:
middle;"><p>${message("templates.reset-password-email.ftl.title")}</p></span></div>
<div class="mktEditable" id="Body Text 1" align="left">
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:18px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<p>${message("templates.reset-password-email.ftl.detail")}</p>
</span>
</div>
<!--Break-->
<table width="100%">
<tr>
<td height="5">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Button-->
<table style="margin:0 auto; " cellspacing="0" cellpadding="0" width="100%">
<tr>
<td style="text-align:center; margin:0 auto; ">
<div><!--[if mso]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w="urn:schemas-microsoft-com:office:word"
style="height:45px;v-text-anchor:middle;width:220px;" stroke="f"
fillcolor="#47AA2">
<w:anchorlock/>
<center>
<![endif]-->
<div class="mktEditable" id="Button Text"><a href="${reset_password_url}" style="background-color:#47aa42; color:#ffffff; display:inline-block; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:18px; font-weight:400; line-height:45px; text-align:center; text-decoration:none; width:220px;
-webkit-text-size-adjust:none;">${message("templates.reset-password-email.ftl.reset_password_button")}</a></div>
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:12px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<p>${message("templates.reset-password-email.ftl.ignore_message")}</p>
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:12px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<hr>
<p>${message("templates.reset-password-email.ftl.having_trouble_clicking_button")}</p>
<p><a href="${reset_password_url}">${reset_password_url}</a></p>
</span>
<!--[if mso]>
</center>
</v:rect>
<![endif]--></div>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="5">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Footer Block-->
<table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
<tr>
<td align="center">
<a style="color: #0c79bf; text-decoration: underline;" href="http://www.alfresco.com/company/contact"
target="_blank"><span
style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #0c79bf;">${message("templates.generic-email.ftl.contact_us")}</span></a>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial,
sans-serif; font-size: 10px; color: #b3b3b8;">&copy; ${date?string["yyyy"]} Alfresco Software, Inc.
${message("templates.generic-email.ftl.copy_right")}</span><br>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #b3b3b8;">Bridge Ave, The Place Maidenhead SL6 1AF United Kingdom</span><br>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #b3b3b8;">1825 S Grant St, Suite 900 San Mateo, CA 94402 USA</span><br>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Litmus Tracking-->
<table style="display: none;">
<tbody>
<tr>
<td style="width: 0px; display: none; overflow: hidden; max-height: 0px;">{{my.Litmus_Code}}</td>
</tr>
</tbody>
</table>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

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