mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-08 14:51:49 +00:00
Merge branch 'master' of github.com:Alfresco/alfresco-community-repo into feature/MNT-24127-EndpointToCalculateFolderSize
# Conflicts: # repository/src/test/java/org/alfresco/AppContext01TestSuite.java
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-amps</artifactId>
|
<artifactId>alfresco-community-repo-amps</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
|
<artifactId>alfresco-governance-services-automation-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@@ -49,6 +49,7 @@ import org.alfresco.rest.rm.community.requests.gscore.api.TransferAPI;
|
|||||||
import org.alfresco.rest.rm.community.requests.gscore.api.TransferContainerAPI;
|
import org.alfresco.rest.rm.community.requests.gscore.api.TransferContainerAPI;
|
||||||
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI;
|
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI;
|
||||||
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledRecordFolderAPI;
|
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledRecordFolderAPI;
|
||||||
|
import org.alfresco.rest.rm.community.requests.gscore.api.RetentionScheduleAPI;
|
||||||
import org.alfresco.utility.data.DataUserAIS;
|
import org.alfresco.utility.data.DataUserAIS;
|
||||||
import org.alfresco.utility.model.RepoTestModel;
|
import org.alfresco.utility.model.RepoTestModel;
|
||||||
import org.alfresco.utility.model.UserModel;
|
import org.alfresco.utility.model.UserModel;
|
||||||
@@ -254,4 +255,14 @@ public class RestAPIFactory
|
|||||||
{
|
{
|
||||||
return getGSCoreAPI(userModel).usingHoldsAPI();
|
return getGSCoreAPI(userModel).usingHoldsAPI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RetentionScheduleAPI getRetentionScheduleAPI()
|
||||||
|
{
|
||||||
|
return getGSCoreAPI(null).usingRetentionScheduleAPI();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RetentionScheduleAPI getRetentionScheduleAPI(UserModel userModel)
|
||||||
|
{
|
||||||
|
return getGSCoreAPI(userModel).usingRetentionScheduleAPI();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.rm.community.model.retentionschedule;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.alfresco.utility.model.TestModel;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retention schedule
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class RetentionSchedule extends TestModel
|
||||||
|
{
|
||||||
|
private String id ;
|
||||||
|
private String parentId;
|
||||||
|
private String authority;
|
||||||
|
private String instructions;
|
||||||
|
private boolean isRecordLevel;
|
||||||
|
private boolean isUnpublishedUpdates;
|
||||||
|
private List<RetentionScheduleActionDefinition> actions;
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.rm.community.model.retentionschedule;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retention schedule action definition
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RetentionScheduleActionDefinition
|
||||||
|
{
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private int periodAmount;
|
||||||
|
private String period;
|
||||||
|
private String periodProperty;
|
||||||
|
private boolean combineDispositionStepConditions;
|
||||||
|
private List<String> events;
|
||||||
|
private boolean eligibleOnFirstCompleteEvent;
|
||||||
|
private String description;
|
||||||
|
private boolean retainRecordMetadataAfterDestruction;
|
||||||
|
private String location;
|
||||||
|
private int index;
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.rm.community.model.retentionschedule;
|
||||||
|
|
||||||
|
import org.alfresco.rest.core.RestModels;
|
||||||
|
public class RetentionScheduleCollection extends RestModels<RetentionScheduleEntry, RetentionScheduleCollection>
|
||||||
|
{
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.rm.community.model.retentionschedule;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.alfresco.rest.core.RestModels;
|
||||||
|
@Data
|
||||||
|
public class RetentionScheduleEntry extends RestModels<RetentionSchedule, RetentionScheduleEntry>
|
||||||
|
{
|
||||||
|
@JsonProperty
|
||||||
|
private RetentionSchedule entry;
|
||||||
|
}
|
@@ -47,6 +47,7 @@ import org.alfresco.rest.rm.community.requests.gscore.api.TransferAPI;
|
|||||||
import org.alfresco.rest.rm.community.requests.gscore.api.TransferContainerAPI;
|
import org.alfresco.rest.rm.community.requests.gscore.api.TransferContainerAPI;
|
||||||
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI;
|
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledContainerAPI;
|
||||||
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledRecordFolderAPI;
|
import org.alfresco.rest.rm.community.requests.gscore.api.UnfiledRecordFolderAPI;
|
||||||
|
import org.alfresco.rest.rm.community.requests.gscore.api.RetentionScheduleAPI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the entire GS Core API
|
* Defines the entire GS Core API
|
||||||
@@ -193,4 +194,9 @@ public class GSCoreAPI extends RMModelRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
public HoldsAPI usingHoldsAPI() { return new HoldsAPI(getRmRestWrapper()); }
|
public HoldsAPI usingHoldsAPI() { return new HoldsAPI(getRmRestWrapper()); }
|
||||||
|
|
||||||
|
public RetentionScheduleAPI usingRetentionScheduleAPI()
|
||||||
|
{
|
||||||
|
return new RetentionScheduleAPI(getRmRestWrapper());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.rm.community.requests.gscore.api;
|
||||||
|
|
||||||
|
import org.alfresco.rest.core.RMRestWrapper;
|
||||||
|
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionSchedule;
|
||||||
|
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionScheduleCollection;
|
||||||
|
import org.alfresco.rest.rm.community.requests.RMModelRequest;
|
||||||
|
|
||||||
|
import static org.alfresco.rest.core.RestRequest.requestWithBody;
|
||||||
|
import static org.alfresco.rest.core.RestRequest.simpleRequest;
|
||||||
|
import static org.alfresco.rest.rm.community.util.ParameterCheck.mandatoryObject;
|
||||||
|
import static org.alfresco.rest.rm.community.util.ParameterCheck.mandatoryString;
|
||||||
|
import static org.alfresco.rest.rm.community.util.PojoUtility.toJson;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.EMPTY;
|
||||||
|
import static org.springframework.http.HttpMethod.GET;
|
||||||
|
import static org.springframework.http.HttpMethod.POST;
|
||||||
|
|
||||||
|
public class RetentionScheduleAPI extends RMModelRequest
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param rmRestWrapper
|
||||||
|
*/
|
||||||
|
public RetentionScheduleAPI(RMRestWrapper rmRestWrapper)
|
||||||
|
{
|
||||||
|
super(rmRestWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a retention schedule.
|
||||||
|
*
|
||||||
|
* @param retentionScheduleModel The retentionSchedule model
|
||||||
|
* @param recordCategoryId The identifier of a record category
|
||||||
|
* @param parameters The URL parameters to add
|
||||||
|
* @return The created {@link RetentionSchedule}
|
||||||
|
* @throws RuntimeException for the following cases:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code recordCategoryId} is not a valid format or {@code recordCategoryId} is invalid</li>
|
||||||
|
* <li>authentication fails</li>
|
||||||
|
* <li>current user does not have permission to add children to {@code recordCategoryId}</li>
|
||||||
|
* <li>{@code recordCategoryId} does not exist</li>
|
||||||
|
* <li>new name clashes with an existing node in the current parent container</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public RetentionSchedule createRetentionSchedule(RetentionSchedule retentionScheduleModel, String recordCategoryId, String parameters)
|
||||||
|
{
|
||||||
|
mandatoryString("recordCategoryId", recordCategoryId);
|
||||||
|
mandatoryObject("retentionScheduleModel", retentionScheduleModel);
|
||||||
|
|
||||||
|
return getRmRestWrapper().processModel(RetentionSchedule.class, requestWithBody(
|
||||||
|
POST,
|
||||||
|
toJson(retentionScheduleModel),
|
||||||
|
"record-categories/{recordCategoryId}/retention-schedules",
|
||||||
|
recordCategoryId,
|
||||||
|
parameters
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #createRetentionSchedule(RetentionSchedule, String, String)}
|
||||||
|
*/
|
||||||
|
public RetentionSchedule createRetentionSchedule(RetentionSchedule retentionScheduleModel, String recordCategoryId)
|
||||||
|
{
|
||||||
|
return createRetentionSchedule(retentionScheduleModel, recordCategoryId, EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the retentionSchedule of a record category.
|
||||||
|
*
|
||||||
|
* @param recordCategoryId The identifier of a record category
|
||||||
|
* @param parameters The URL parameters to add
|
||||||
|
* @return The {@link RetentionSchedule} for the given {@code recordCategoryId}
|
||||||
|
* @throws RuntimeException for the following cases:
|
||||||
|
* <ul>
|
||||||
|
* <li>authentication fails</li>
|
||||||
|
* <li>current user does not have permission to read {@code recordCategoryId}</li>
|
||||||
|
* <li>{@code recordCategoryId} does not exist</li>
|
||||||
|
*</ul>
|
||||||
|
*/
|
||||||
|
public RetentionScheduleCollection getRetentionSchedule(String recordCategoryId, String parameters)
|
||||||
|
{
|
||||||
|
mandatoryString("recordCategoryId", recordCategoryId);
|
||||||
|
|
||||||
|
return getRmRestWrapper().processModels(RetentionScheduleCollection.class, simpleRequest(
|
||||||
|
GET,
|
||||||
|
"record-categories/{recordCategoryId}/retention-schedules?{parameters}",
|
||||||
|
recordCategoryId,
|
||||||
|
parameters
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link #getRetentionSchedule(String, String)}
|
||||||
|
*/
|
||||||
|
public RetentionScheduleCollection getRetentionSchedule(String recordCategoryId)
|
||||||
|
{
|
||||||
|
return getRetentionSchedule(recordCategoryId, EMPTY);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,270 @@
|
|||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rest.rm.community.retentionschedule;
|
||||||
|
|
||||||
|
import org.alfresco.rest.rm.community.base.BaseRMRestTest;
|
||||||
|
import org.alfresco.rest.rm.community.model.recordcategory.RecordCategory;
|
||||||
|
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionSchedule;
|
||||||
|
import org.alfresco.rest.rm.community.model.retentionschedule.RetentionScheduleCollection;
|
||||||
|
import org.alfresco.rest.v0.RMRolesAndActionsAPI;
|
||||||
|
import org.alfresco.utility.model.UserModel;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.testng.annotations.AfterClass;
|
||||||
|
import org.testng.annotations.BeforeClass;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import static org.alfresco.rest.core.v0.BaseAPI.RM_SITE_ID;
|
||||||
|
import static org.alfresco.utility.data.RandomData.getRandomAlphanumeric;
|
||||||
|
import static org.alfresco.utility.data.RandomData.getRandomName;
|
||||||
|
import static org.springframework.http.HttpStatus.CONFLICT;
|
||||||
|
import static org.springframework.http.HttpStatus.CREATED;
|
||||||
|
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||||
|
import static org.springframework.http.HttpStatus.NOT_FOUND;
|
||||||
|
import static org.springframework.http.HttpStatus.OK;
|
||||||
|
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
|
||||||
|
import static org.testng.Assert.assertFalse;
|
||||||
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
import static org.testng.AssertJUnit.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class contains the tests for the Retention Schedule CRUD V1 API
|
||||||
|
*/
|
||||||
|
public class RetentionScheduleTests extends BaseRMRestTest
|
||||||
|
{
|
||||||
|
private RecordCategory recordCategory;
|
||||||
|
private RetentionSchedule createdRetentionSchedule;
|
||||||
|
private UserModel nonRMuser;
|
||||||
|
@Autowired
|
||||||
|
private RMRolesAndActionsAPI rmRolesAndActionsAPI;
|
||||||
|
|
||||||
|
@BeforeClass(alwaysRun = true)
|
||||||
|
public void preconditionForRetentionScheduleTests()
|
||||||
|
{
|
||||||
|
createRMSiteIfNotExists();
|
||||||
|
// create a non rm user
|
||||||
|
nonRMuser = dataUser.createRandomTestUser("testUser");
|
||||||
|
//Create record category
|
||||||
|
recordCategory = createRootCategory(getRandomName("recordCategory"));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category exists
|
||||||
|
* When I ask the API to create a retention schedule with a user having no rights
|
||||||
|
* Then it will give 403 as status code
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 1)
|
||||||
|
public void createRetentionScheduleFor403()
|
||||||
|
{
|
||||||
|
RetentionSchedule retentionSchedule = new RetentionSchedule();
|
||||||
|
|
||||||
|
// Create retention schedule with user having no rights
|
||||||
|
getRestAPIFactory().getRetentionScheduleAPI(nonRMuser).createRetentionSchedule(retentionSchedule, recordCategory.getId());
|
||||||
|
|
||||||
|
// Verify the status code
|
||||||
|
assertStatusCode(FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category does not exists
|
||||||
|
* When I ask the API to create a retention schedule on a category Id
|
||||||
|
* Then it will give 404 as a status code
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 2)
|
||||||
|
public void createRetentionScheduleFor404()
|
||||||
|
{
|
||||||
|
RetentionSchedule retentionSchedule = new RetentionSchedule();
|
||||||
|
|
||||||
|
//Create retention schedule with category id not exist
|
||||||
|
getRestAPIFactory().getRetentionScheduleAPI().createRetentionSchedule(retentionSchedule, getRandomAlphanumeric());
|
||||||
|
|
||||||
|
// Verify the status code
|
||||||
|
assertStatusCode(NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category exists
|
||||||
|
* When I ask the API to create a retention schedule on a category id with a user having unauthorized access
|
||||||
|
* Then it will give 401 as a status code
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 3)
|
||||||
|
public void createRetentionScheduleFor401()
|
||||||
|
{
|
||||||
|
RetentionSchedule retentionSchedule = new RetentionSchedule();
|
||||||
|
|
||||||
|
//Create retention schedule with a user with unauthorized access
|
||||||
|
createdRetentionSchedule = getRestAPIFactory().getRetentionScheduleAPI(new UserModel(getAdminUser().getUsername(), "wrongPassword")).createRetentionSchedule(retentionSchedule, recordCategory.getId());
|
||||||
|
|
||||||
|
// Verify the status code
|
||||||
|
assertStatusCode(UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category exists
|
||||||
|
* When I ask the API to create a retention schedule with a user having access
|
||||||
|
* Then it is created with a 201 status code
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 4)
|
||||||
|
public void createRetentionScheduleFor201()
|
||||||
|
{
|
||||||
|
RetentionSchedule retentionSchedule = new RetentionSchedule();
|
||||||
|
String authority = "authority" + getRandomAlphanumeric();
|
||||||
|
String instructions = "instructions" + getRandomAlphanumeric();
|
||||||
|
boolean isRecordLevel = false;
|
||||||
|
retentionSchedule.setAuthority(authority);
|
||||||
|
retentionSchedule.setInstructions(instructions);
|
||||||
|
retentionSchedule.setRecordLevel(isRecordLevel);
|
||||||
|
|
||||||
|
//Create retention schedule with a valid user
|
||||||
|
createdRetentionSchedule = getRestAPIFactory().getRetentionScheduleAPI()
|
||||||
|
.createRetentionSchedule(retentionSchedule, recordCategory.getId());
|
||||||
|
|
||||||
|
// Verify the status code
|
||||||
|
assertStatusCode(CREATED);
|
||||||
|
assertEquals(createdRetentionSchedule.getAuthority(), authority);
|
||||||
|
assertEquals(createdRetentionSchedule.getInstructions(), instructions);
|
||||||
|
assertFalse(createdRetentionSchedule.isRecordLevel());
|
||||||
|
assertNotNull(createdRetentionSchedule.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category exists
|
||||||
|
* When I ask the API to create a retention schedule on a category id having retention schedule already
|
||||||
|
* Then it will give 409 as a status code
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 5)
|
||||||
|
public void createRetentionScheduleFor409()
|
||||||
|
{
|
||||||
|
RetentionSchedule retentionSchedule = new RetentionSchedule();
|
||||||
|
//Create retention schedule on a category with already having retention schedule
|
||||||
|
getRestAPIFactory().getRetentionScheduleAPI()
|
||||||
|
.createRetentionSchedule(retentionSchedule, recordCategory.getId());
|
||||||
|
|
||||||
|
assertStatusCode(CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category exists
|
||||||
|
* When I ask the API to get a retention schedule on a given categoryId with a user having no rights
|
||||||
|
* Then it will give 403
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 6)
|
||||||
|
public void retentionScheduleWith403()
|
||||||
|
{
|
||||||
|
//Get retention schedule with user having no rights
|
||||||
|
getRestAPIFactory().getRetentionScheduleAPI(nonRMuser).getRetentionSchedule(recordCategory.getId());
|
||||||
|
|
||||||
|
// Verify the status code
|
||||||
|
assertStatusCode(FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category does not exists
|
||||||
|
* When I ask the API to get a retention schedule on a category Id
|
||||||
|
* Then it will give 404 as a status code
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 7)
|
||||||
|
public void retentionScheduleWith404()
|
||||||
|
{
|
||||||
|
|
||||||
|
//Get retention schedule with category id that does not exist
|
||||||
|
getRestAPIFactory().getRetentionScheduleAPI().getRetentionSchedule(getRandomAlphanumeric());
|
||||||
|
|
||||||
|
// Verify the status code
|
||||||
|
assertStatusCode(NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category exists
|
||||||
|
* When I ask the API to get a retention schedule on a categoryId with a user having unauthorized access
|
||||||
|
* Then it will give 401 as a status code
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 8)
|
||||||
|
public void retentionScheduleWith401()
|
||||||
|
{
|
||||||
|
//Create retention schedule with a user with unauthorized access
|
||||||
|
getRestAPIFactory().getRetentionScheduleAPI(new UserModel(getAdminUser().getUsername(), "wrongPassword")).getRetentionSchedule(recordCategory.getId());
|
||||||
|
|
||||||
|
// Verify the status code
|
||||||
|
assertStatusCode(UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Given that a record category exists
|
||||||
|
* When I ask the API to get a retention schedule on a categoryId with a user having access
|
||||||
|
* Then it will give retentionSchedule with 200 as a status code
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Test(priority = 9)
|
||||||
|
public void retentionScheduleWith200()
|
||||||
|
{
|
||||||
|
RetentionScheduleCollection retentionScheduleCollection = getRestAPIFactory().getRetentionScheduleAPI().getRetentionSchedule(recordCategory.getId());
|
||||||
|
// Verify the status code
|
||||||
|
assertStatusCode(OK);
|
||||||
|
retentionScheduleCollection.getEntries().forEach(c ->
|
||||||
|
{
|
||||||
|
RetentionSchedule retentionSchedule = c.getEntry();
|
||||||
|
String retentionScheduleId = retentionSchedule.getId();
|
||||||
|
assertNotNull(retentionScheduleId);
|
||||||
|
logger.info("Checking retention schedule " + retentionScheduleId);
|
||||||
|
|
||||||
|
// Find this retention schedule is created one or not
|
||||||
|
assertEquals(createdRetentionSchedule.getId(), retentionScheduleId);
|
||||||
|
assertEquals(createdRetentionSchedule.getParentId(),retentionSchedule.getParentId());
|
||||||
|
assertEquals(createdRetentionSchedule.getAuthority(), retentionSchedule.getAuthority());
|
||||||
|
assertEquals(createdRetentionSchedule.getInstructions(), retentionSchedule.getInstructions());
|
||||||
|
assertEquals(createdRetentionSchedule.isRecordLevel(), retentionSchedule.isRecordLevel());
|
||||||
|
assertEquals(createdRetentionSchedule.isUnpublishedUpdates(), retentionSchedule.isUnpublishedUpdates());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass(alwaysRun = true)
|
||||||
|
public void cleanUpRetentionScheduleTests()
|
||||||
|
{
|
||||||
|
rmRolesAndActionsAPI.deleteAllItemsInContainer(getDataUser().usingAdmin().getAdminUser().getUsername(),
|
||||||
|
getDataUser().usingAdmin().getAdminUser().getPassword(), RM_SITE_ID, recordCategory.getName());
|
||||||
|
deleteRecordCategory(recordCategory.getId());
|
||||||
|
dataUser.deleteUser(nonRMuser);
|
||||||
|
}
|
||||||
|
}
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
<artifactId>alfresco-governance-services-community-parent</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@@ -31,6 +31,11 @@
|
|||||||
<cm:description>Configuration information for the Records Management application.</cm:description>
|
<cm:description>Configuration information for the Records Management application.</cm:description>
|
||||||
</view:properties>
|
</view:properties>
|
||||||
|
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
|
|
||||||
<view:associations>
|
<view:associations>
|
||||||
<cm:contains>
|
<cm:contains>
|
||||||
|
|
||||||
|
@@ -147,6 +147,13 @@
|
|||||||
<property name="transactionService" ref="transactionService" />
|
<property name="transactionService" ref="transactionService" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean class="org.alfresco.rm.rest.api.retentionschedule.RetentionScheduleRelation">
|
||||||
|
<property name="apiUtils" ref="apiUtils" />
|
||||||
|
<property name="nodesModelFactory" ref="nodesModelFactory" />
|
||||||
|
<property name="dispositionService" ref="DispositionService"/>
|
||||||
|
<property name="nodeService" ref="NodeService"/>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean class="org.alfresco.rm.rest.api.recordfolders.RecordFolderEntityResource">
|
<bean class="org.alfresco.rm.rest.api.recordfolders.RecordFolderEntityResource">
|
||||||
<property name="apiUtils" ref="apiUtils" />
|
<property name="apiUtils" ref="apiUtils" />
|
||||||
<property name="fileFolderService" ref="FileFolderService" />
|
<property name="fileFolderService" ref="FileFolderService" />
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
@@ -59,6 +59,9 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
|||||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
|
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
|
||||||
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
||||||
|
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
|
||||||
|
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
|
||||||
|
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
|
||||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
@@ -198,7 +201,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
/**
|
/**
|
||||||
* Behavior to initialize the disposition schedule of a newly filed record.
|
* Behavior to initialize the disposition schedule of a newly filed record.
|
||||||
*
|
*
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.RecordsManagementPolicies.OnFileRecord#onFileRecord(org.alfresco.service.cmr.repository.NodeRef)
|
* @see RecordsManagementPolicies.OnFileRecord#onFileRecord(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Behaviour(kind=BehaviourKind.CLASS, type="rma:record")
|
@Behaviour(kind=BehaviourKind.CLASS, type="rma:record")
|
||||||
@@ -216,7 +219,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#refreshDispositionAction(NodeRef)
|
* @see DispositionService#refreshDispositionAction(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void refreshDispositionAction(NodeRef nodeRef)
|
public void refreshDispositionAction(NodeRef nodeRef)
|
||||||
@@ -242,7 +245,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
/** ========= Disposition Property Methods ========= */
|
/** ========= Disposition Property Methods ========= */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#registerDispositionProperty(org.alfresco.module.org_alfresco_module_rm.disposition.property.DispositionProperty)
|
* @see DispositionService#registerDispositionProperty(DispositionProperty)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void registerDispositionProperty(DispositionProperty dispositionProperty)
|
public void registerDispositionProperty(DispositionProperty dispositionProperty)
|
||||||
@@ -251,7 +254,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getDispositionProperties(boolean, java.lang.String)
|
* @see DispositionService#getDispositionProperties(boolean, String)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<DispositionProperty> getDispositionProperties(boolean isRecordLevel, String dispositionAction)
|
public Collection<DispositionProperty> getDispositionProperties(boolean isRecordLevel, String dispositionAction)
|
||||||
@@ -270,7 +273,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getDispositionProperties()
|
* @see DispositionService#getDispositionProperties()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Collection<DispositionProperty> getDispositionProperties()
|
public Collection<DispositionProperty> getDispositionProperties()
|
||||||
@@ -281,12 +284,11 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
/** ========= Disposition Schedule Methods ========= */
|
/** ========= Disposition Schedule Methods ========= */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getDispositionSchedule(org.alfresco.service.cmr.repository.NodeRef)
|
* @see DispositionService#getDispositionSchedule(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DispositionSchedule getDispositionSchedule(final NodeRef nodeRef)
|
public DispositionSchedule getDispositionSchedule(final NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
DispositionSchedule ds = null;
|
|
||||||
NodeRef dsNodeRef = null;
|
NodeRef dsNodeRef = null;
|
||||||
if (isRecord(nodeRef))
|
if (isRecord(nodeRef))
|
||||||
{
|
{
|
||||||
@@ -311,36 +313,33 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
if (dsNextAction != null)
|
if (dsNextAction != null)
|
||||||
{
|
{
|
||||||
final NodeRef action = dsNextAction.getNextActionNodeRef();
|
final NodeRef action = dsNextAction.getNextActionNodeRef();
|
||||||
if (isNotTrue((Boolean)nodeService.getProperty(action, PROP_MANUALLY_SET_AS_OF)))
|
if (isNotTrue((Boolean)nodeService.getProperty(action, PROP_MANUALLY_SET_AS_OF)) && !dsNextAction.getWriteMode().equals(WriteMode.READ_ONLY))
|
||||||
{
|
{
|
||||||
if (!dsNextAction.getWriteMode().equals(WriteMode.READ_ONLY))
|
final String dispositionActionName = dsNextAction.getNextActionName();
|
||||||
|
final Date dispositionActionDate = dsNextAction.getNextActionDateAsOf();
|
||||||
|
|
||||||
|
RunAsWork<Void> runAsWork = () -> {
|
||||||
|
nodeService.setProperty(action, PROP_DISPOSITION_AS_OF, dispositionActionDate);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// if the current transaction is READ ONLY set the property on the node
|
||||||
|
// in a READ WRITE transaction
|
||||||
|
if (AlfrescoTransactionSupport.getTransactionReadState().equals(TxnReadState.TXN_READ_ONLY))
|
||||||
{
|
{
|
||||||
final String dispositionActionName = dsNextAction.getNextActionName();
|
transactionService.getRetryingTransactionHelper().doInTransaction((RetryingTransactionCallback<Void>) () -> {
|
||||||
final Date dispositionActionDate = dsNextAction.getNextActionDateAsOf();
|
|
||||||
|
|
||||||
RunAsWork<Void> runAsWork = () -> {
|
|
||||||
nodeService.setProperty(action, PROP_DISPOSITION_AS_OF, dispositionActionDate);
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// if the current transaction is READ ONLY set the property on the node
|
|
||||||
// in a READ WRITE transaction
|
|
||||||
if (AlfrescoTransactionSupport.getTransactionReadState().equals(TxnReadState.TXN_READ_ONLY))
|
|
||||||
{
|
|
||||||
transactionService.getRetryingTransactionHelper().doInTransaction((RetryingTransactionCallback<Void>) () -> {
|
|
||||||
AuthenticationUtil.runAsSystem(runAsWork);
|
|
||||||
return null;
|
|
||||||
}, false, true);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AuthenticationUtil.runAsSystem(runAsWork);
|
AuthenticationUtil.runAsSystem(runAsWork);
|
||||||
}
|
return null;
|
||||||
|
}, false, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AuthenticationUtil.runAsSystem(runAsWork);
|
||||||
|
}
|
||||||
|
|
||||||
if (dsNextAction.getWriteMode().equals(WriteMode.DATE_AND_NAME))
|
if (dsNextAction.getWriteMode().equals(WriteMode.DATE_AND_NAME))
|
||||||
{
|
{
|
||||||
nodeService.setProperty(action, PROP_DISPOSITION_ACTION_NAME, dispositionActionName);
|
nodeService.setProperty(action, PROP_DISPOSITION_ACTION_NAME, dispositionActionName);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +351,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
// Get the disposition instructions for the node reference provided
|
// Get the disposition instructions for the node reference provided
|
||||||
dsNodeRef = getDispositionScheduleImpl(nodeRef);
|
dsNodeRef = getDispositionScheduleImpl(nodeRef);
|
||||||
}
|
}
|
||||||
|
DispositionSchedule ds = null;
|
||||||
if (dsNodeRef != null)
|
if (dsNodeRef != null)
|
||||||
{
|
{
|
||||||
ds = new DispositionScheduleImpl(serviceRegistry, nodeService, dsNodeRef);
|
ds = new DispositionScheduleImpl(serviceRegistry, nodeService, dsNodeRef);
|
||||||
@@ -382,7 +381,8 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public DispositionSchedule getOriginDispositionSchedule(NodeRef nodeRef)
|
public DispositionSchedule getOriginDispositionSchedule(NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
NodeRef parent = this.nodeService.getPrimaryParent(nodeRef).getParentRef();
|
NodeRef parent = this.nodeService.getPrimaryParent(nodeRef).getParentRef();
|
||||||
@@ -406,7 +406,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getAssociatedDispositionSchedule(org.alfresco.service.cmr.repository.NodeRef)
|
* @see DispositionService#getAssociatedDispositionSchedule(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DispositionSchedule getAssociatedDispositionSchedule(NodeRef nodeRef)
|
public DispositionSchedule getAssociatedDispositionSchedule(NodeRef nodeRef)
|
||||||
@@ -437,7 +437,6 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
*/
|
*/
|
||||||
private NodeRef getAssociatedDispositionScheduleImpl(NodeRef nodeRef)
|
private NodeRef getAssociatedDispositionScheduleImpl(NodeRef nodeRef)
|
||||||
{
|
{
|
||||||
NodeRef result = null;
|
|
||||||
ParameterCheck.mandatory("nodeRef", nodeRef);
|
ParameterCheck.mandatory("nodeRef", nodeRef);
|
||||||
|
|
||||||
// Make sure we are dealing with an RM node
|
// Make sure we are dealing with an RM node
|
||||||
@@ -445,6 +444,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
{
|
{
|
||||||
throw new AlfrescoRuntimeException("Can not find the associated retention schedule for a non records management component. (nodeRef=" + nodeRef.toString() + ")");
|
throw new AlfrescoRuntimeException("Can not find the associated retention schedule for a non records management component. (nodeRef=" + nodeRef.toString() + ")");
|
||||||
}
|
}
|
||||||
|
NodeRef result = null;
|
||||||
if (getInternalNodeService().hasAspect(nodeRef, ASPECT_SCHEDULED))
|
if (getInternalNodeService().hasAspect(nodeRef, ASPECT_SCHEDULED))
|
||||||
{
|
{
|
||||||
List<ChildAssociationRef> childAssocs = getInternalNodeService().getChildAssocs(nodeRef, ASSOC_DISPOSITION_SCHEDULE, RegexQNamePattern.MATCH_ALL);
|
List<ChildAssociationRef> childAssocs = getInternalNodeService().getChildAssocs(nodeRef, ASSOC_DISPOSITION_SCHEDULE, RegexQNamePattern.MATCH_ALL);
|
||||||
@@ -459,7 +459,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getAssociatedRecordsManagementContainer(org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule)
|
* @see DispositionService#getAssociatedRecordsManagementContainer(DispositionSchedule)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public NodeRef getAssociatedRecordsManagementContainer(DispositionSchedule dispositionSchedule)
|
public NodeRef getAssociatedRecordsManagementContainer(DispositionSchedule dispositionSchedule)
|
||||||
@@ -477,12 +477,9 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
{
|
{
|
||||||
// TODO in the future we should be able to support disposition schedule reuse, but for now just warn that
|
// TODO in the future we should be able to support disposition schedule reuse, but for now just warn that
|
||||||
// only the first disposition schedule will be considered
|
// only the first disposition schedule will be considered
|
||||||
if (LOGGER.isWarnEnabled())
|
LOGGER.atWarn().log("Retention schedule has more than one associated records management container. " +
|
||||||
{
|
"This is not currently supported so only the first container will be considered. " +
|
||||||
LOGGER.warn("Retention schedule has more than one associated records management container. " +
|
"(dispositionScheduleNodeRef={})", dispositionSchedule.getNodeRef());
|
||||||
"This is not currently supported so only the first container will be considered. " +
|
|
||||||
"(dispositionScheduleNodeRef=" + dispositionSchedule.getNodeRef().toString() + ")");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the container reference
|
// Get the container reference
|
||||||
@@ -495,7 +492,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#hasDisposableItems(org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule)
|
* @see DispositionService#hasDisposableItems(DispositionSchedule)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean hasDisposableItems(DispositionSchedule dispositionSchdule)
|
public boolean hasDisposableItems(DispositionSchedule dispositionSchdule)
|
||||||
@@ -537,19 +534,16 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (filePlanService.isRecordCategory(item) && getAssociatedDispositionScheduleImpl(item) == null)
|
else if (filePlanService.isRecordCategory(item) && getAssociatedDispositionScheduleImpl(item) == null && hasDisposableItemsImpl(isRecordLevelDisposition, item))
|
||||||
{
|
{
|
||||||
if (hasDisposableItemsImpl(isRecordLevelDisposition, item));
|
return true;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getDisposableItems(org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule)
|
* @see DispositionService#getDisposableItems(DispositionSchedule)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<NodeRef> getDisposableItems(DispositionSchedule dispositionSchedule)
|
public List<NodeRef> getDisposableItems(DispositionSchedule dispositionSchedule)
|
||||||
@@ -564,7 +558,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#isDisposableItem(org.alfresco.service.cmr.repository.NodeRef)
|
* @see DispositionService#isDisposableItem(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isDisposableItem(NodeRef nodeRef)
|
public boolean isDisposableItem(NodeRef nodeRef)
|
||||||
@@ -604,20 +598,18 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#createDispositionSchedule(org.alfresco.service.cmr.repository.NodeRef, java.util.Map)
|
* @see DispositionService#createDispositionSchedule(NodeRef, Map)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DispositionSchedule createDispositionSchedule(NodeRef nodeRef, Map<QName, Serializable> props)
|
public DispositionSchedule createDispositionSchedule(NodeRef nodeRef, Map<QName, Serializable> props)
|
||||||
{
|
{
|
||||||
NodeRef dsNodeRef = null;
|
|
||||||
|
|
||||||
// Check mandatory parameters
|
// Check mandatory parameters
|
||||||
ParameterCheck.mandatory("nodeRef", nodeRef);
|
ParameterCheck.mandatory("nodeRef", nodeRef);
|
||||||
|
|
||||||
// Check exists
|
// Check exists
|
||||||
if (!nodeService.exists(nodeRef))
|
if (!nodeService.exists(nodeRef))
|
||||||
{
|
{
|
||||||
throw new AlfrescoRuntimeException("Unable to create retention schedule, because node does not exist. (nodeRef=" + nodeRef.toString() + ")");
|
throw new EntityNotFoundException(nodeRef.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check is sub-type of rm:recordCategory
|
// Check is sub-type of rm:recordCategory
|
||||||
@@ -625,10 +617,12 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
if (!TYPE_RECORD_CATEGORY.equals(nodeRefType) &&
|
if (!TYPE_RECORD_CATEGORY.equals(nodeRefType) &&
|
||||||
!dictionaryService.isSubClass(nodeRefType, TYPE_RECORD_CATEGORY))
|
!dictionaryService.isSubClass(nodeRefType, TYPE_RECORD_CATEGORY))
|
||||||
{
|
{
|
||||||
throw new AlfrescoRuntimeException("Unable to create retention schedule on a node that is not a records management container.");
|
throw new InvalidArgumentException("The given id:'" + nodeRef.getId() + "' (nodeType:" + nodeRef
|
||||||
|
+ ") is not valid. Expected nodeType is:" + TYPE_RECORD_CATEGORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
behaviourFilter.disableBehaviour(nodeRef, ASPECT_SCHEDULED);
|
behaviourFilter.disableBehaviour(nodeRef, ASPECT_SCHEDULED);
|
||||||
|
NodeRef dsNodeRef = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Add the schedules aspect if required
|
// Add the schedules aspect if required
|
||||||
@@ -662,7 +656,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Error since the node already has a disposition schedule set
|
// Error since the node already has a disposition schedule set
|
||||||
throw new AlfrescoRuntimeException("Unable to create retention schedule on node that already has a retention schedule.");
|
throw new ConstraintViolatedException("Unable to create retention schedule on node that already has a retention schedule.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -686,7 +680,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
{
|
{
|
||||||
// make sure at least a name has been defined
|
// make sure at least a name has been defined
|
||||||
String name = (String)actionDefinitionParams.get(PROP_DISPOSITION_ACTION_NAME);
|
String name = (String)actionDefinitionParams.get(PROP_DISPOSITION_ACTION_NAME);
|
||||||
if (name == null || name.length() == 0)
|
if (name == null || name.isEmpty())
|
||||||
{
|
{
|
||||||
throw new IllegalArgumentException("'name' parameter is mandatory when creating a disposition action definition");
|
throw new IllegalArgumentException("'name' parameter is mandatory when creating a disposition action definition");
|
||||||
}
|
}
|
||||||
@@ -695,10 +689,10 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
|
|
||||||
// create the child association from the schedule to the action definition
|
// create the child association from the schedule to the action definition
|
||||||
NodeRef actionNodeRef = this.nodeService.createNode(schedule.getNodeRef(),
|
NodeRef actionNodeRef = this.nodeService.createNode(schedule.getNodeRef(),
|
||||||
RecordsManagementModel.ASSOC_DISPOSITION_ACTION_DEFINITIONS,
|
ASSOC_DISPOSITION_ACTION_DEFINITIONS,
|
||||||
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
|
QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI,
|
||||||
QName.createValidLocalName(name)),
|
QName.createValidLocalName(name)),
|
||||||
RecordsManagementModel.TYPE_DISPOSITION_ACTION_DEFINITION, actionDefinitionParams).getChildRef();
|
TYPE_DISPOSITION_ACTION_DEFINITION, actionDefinitionParams).getChildRef();
|
||||||
|
|
||||||
// get the updated disposition schedule and retrieve the new action definition
|
// get the updated disposition schedule and retrieve the new action definition
|
||||||
NodeRef scheduleParent = this.nodeService.getPrimaryParent(schedule.getNodeRef()).getParentRef();
|
NodeRef scheduleParent = this.nodeService.getPrimaryParent(schedule.getNodeRef()).getParentRef();
|
||||||
@@ -707,7 +701,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#removeDispositionActionDefinition(org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule, org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition)
|
* @see DispositionService#removeDispositionActionDefinition(DispositionSchedule, DispositionActionDefinition)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void removeDispositionActionDefinition(DispositionSchedule schedule, DispositionActionDefinition actionDefinition)
|
public void removeDispositionActionDefinition(DispositionSchedule schedule, DispositionActionDefinition actionDefinition)
|
||||||
@@ -777,16 +771,12 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
|
|
||||||
DispositionAction da;
|
DispositionAction da;
|
||||||
// check if current transaction is a READ ONLY one and if true create the node in a READ WRITE transaction
|
// check if current transaction is a READ ONLY one and if true create the node in a READ WRITE transaction
|
||||||
if (AlfrescoTransactionSupport.getTransactionReadState().equals(TxnReadState.TXN_READ_ONLY))
|
if (AlfrescoTransactionSupport.getTransactionReadState().equals(TxnReadState.TXN_READ_ONLY)) {
|
||||||
{
|
da = transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||||
da =
|
() -> createDispositionAction(nodeRef, props),
|
||||||
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<DispositionAction>()
|
false,
|
||||||
{
|
true
|
||||||
public DispositionAction execute() throws Throwable
|
);
|
||||||
{
|
|
||||||
return createDispositionAction(nodeRef, props);
|
|
||||||
}
|
|
||||||
}, false, true);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -836,13 +826,13 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
Period period = dispositionActionDefinition.getPeriod();
|
Period period = dispositionActionDefinition.getPeriod();
|
||||||
if (period != null)
|
if (period != null)
|
||||||
{
|
{
|
||||||
Date contextDate = null;
|
Date contextDate;
|
||||||
|
|
||||||
// Get the period properties value
|
// Get the period properties value
|
||||||
QName periodProperty = dispositionActionDefinition.getPeriodProperty();
|
QName periodProperty = dispositionActionDefinition.getPeriodProperty();
|
||||||
if (periodProperty != null)
|
if (periodProperty != null)
|
||||||
{
|
{
|
||||||
if (RecordsManagementModel.PROP_DISPOSITION_AS_OF.equals(periodProperty))
|
if (PROP_DISPOSITION_AS_OF.equals(periodProperty))
|
||||||
{
|
{
|
||||||
DispositionAction lastCompletedDispositionAction = getLastCompletedDispostionAction(nodeRef);
|
DispositionAction lastCompletedDispositionAction = getLastCompletedDispostionAction(nodeRef);
|
||||||
if (lastCompletedDispositionAction != null)
|
if (lastCompletedDispositionAction != null)
|
||||||
@@ -886,7 +876,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#isNextDispositionActionEligible(org.alfresco.service.cmr.repository.NodeRef)
|
* @see DispositionService#isNextDispositionActionEligible(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isNextDispositionActionEligible(NodeRef nodeRef)
|
public boolean isNextDispositionActionEligible(NodeRef nodeRef)
|
||||||
@@ -940,7 +930,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
{
|
{
|
||||||
NodeRef eventExecution = assoc.getChildRef();
|
NodeRef eventExecution = assoc.getChildRef();
|
||||||
Boolean isCompleteValue = (Boolean) getInternalNodeService().getProperty(eventExecution, PROP_EVENT_EXECUTION_COMPLETE);
|
Boolean isCompleteValue = (Boolean) getInternalNodeService().getProperty(eventExecution, PROP_EVENT_EXECUTION_COMPLETE);
|
||||||
boolean isComplete = false;
|
boolean isComplete;
|
||||||
if (isCompleteValue != null)
|
if (isCompleteValue != null)
|
||||||
{
|
{
|
||||||
isComplete = isCompleteValue.booleanValue();
|
isComplete = isCompleteValue.booleanValue();
|
||||||
@@ -987,7 +977,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getNextDispositionAction(org.alfresco.service.cmr.repository.NodeRef)
|
* @see DispositionService#getNextDispositionAction(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DispositionAction getNextDispositionAction(NodeRef nodeRef)
|
public DispositionAction getNextDispositionAction(NodeRef nodeRef)
|
||||||
@@ -1006,7 +996,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
/** ========= Disposition Action History Methods ========= */
|
/** ========= Disposition Action History Methods ========= */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getCompletedDispositionActions(org.alfresco.service.cmr.repository.NodeRef)
|
* @see DispositionService#getCompletedDispositionActions(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<DispositionAction> getCompletedDispositionActions(NodeRef nodeRef)
|
public List<DispositionAction> getCompletedDispositionActions(NodeRef nodeRef)
|
||||||
@@ -1022,7 +1012,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#getLastCompletedDispostionAction(org.alfresco.service.cmr.repository.NodeRef)
|
* @see DispositionService#getLastCompletedDispostionAction(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public DispositionAction getLastCompletedDispostionAction(NodeRef nodeRef)
|
public DispositionAction getLastCompletedDispostionAction(NodeRef nodeRef)
|
||||||
@@ -1038,7 +1028,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#isDisposableItemCutoff(NodeRef)
|
* @see DispositionService#isDisposableItemCutoff(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isDisposableItemCutoff(NodeRef nodeRef)
|
public boolean isDisposableItemCutoff(NodeRef nodeRef)
|
||||||
@@ -1048,7 +1038,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#updateNextDispositionAction(NodeRef)
|
* @see DispositionService#updateNextDispositionAction(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void updateNextDispositionAction(final NodeRef nodeRef)
|
public void updateNextDispositionAction(final NodeRef nodeRef)
|
||||||
@@ -1058,7 +1048,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
RunAsWork<Void> runAsWork = new RunAsWork<Void>()
|
RunAsWork<Void> runAsWork = new RunAsWork<Void>()
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork#doWork()
|
* @see RunAsWork#doWork()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Void doWork()
|
public Void doWork()
|
||||||
@@ -1077,7 +1067,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#updateNextDispositionAction(NodeRef)
|
* @see DispositionService#updateNextDispositionAction(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void updateNextDispositionAction(final NodeRef nodeRef, final DispositionSchedule dispositionSchedule)
|
public void updateNextDispositionAction(final NodeRef nodeRef, final DispositionSchedule dispositionSchedule)
|
||||||
@@ -1087,7 +1077,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
RunAsWork<Void> runAsWork = new RunAsWork<Void>()
|
RunAsWork<Void> runAsWork = new RunAsWork<Void>()
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork#doWork()
|
* @see RunAsWork#doWork()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Void doWork()
|
public Void doWork()
|
||||||
@@ -1113,16 +1103,13 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<DispositionActionDefinition> dispositionActionDefinitions = dispositionSchedule.getDispositionActionDefinitions();
|
List<DispositionActionDefinition> dispositionActionDefinitions = dispositionSchedule.getDispositionActionDefinitions();
|
||||||
DispositionActionDefinition currentDispositionActionDefinition = null;
|
DispositionActionDefinition currentDispositionActionDefinition;
|
||||||
DispositionActionDefinition nextDispositionActionDefinition = null;
|
DispositionActionDefinition nextDispositionActionDefinition = null;
|
||||||
|
|
||||||
if (currentDispositionAction == null)
|
if (currentDispositionAction == null && !dispositionActionDefinitions.isEmpty())
|
||||||
{
|
{
|
||||||
if (!dispositionActionDefinitions.isEmpty())
|
// The next disposition action is the first action
|
||||||
{
|
nextDispositionActionDefinition = dispositionActionDefinitions.get(0);
|
||||||
// The next disposition action is the first action
|
|
||||||
nextDispositionActionDefinition = dispositionActionDefinitions.get(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1167,7 +1154,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService#cutoffDisposableItem(NodeRef)
|
* @see DispositionService#cutoffDisposableItem(NodeRef)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void cutoffDisposableItem(final NodeRef nodeRef)
|
public void cutoffDisposableItem(final NodeRef nodeRef)
|
||||||
@@ -1205,6 +1192,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
// runAs system so that we can close a record that has already been cutoff
|
// runAs system so that we can close a record that has already been cutoff
|
||||||
authenticationUtil.runAsSystem(new RunAsWork<Void>()
|
authenticationUtil.runAsSystem(new RunAsWork<Void>()
|
||||||
{
|
{
|
||||||
|
@Override
|
||||||
public Void doWork() throws Exception
|
public Void doWork() throws Exception
|
||||||
{
|
{
|
||||||
recordFolderService.closeRecordFolder(nodeRef);
|
recordFolderService.closeRecordFolder(nodeRef);
|
||||||
@@ -1224,6 +1212,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public Date getDispositionActionDate(NodeRef record, NodeRef dispositionSchedule, String dispositionActionName)
|
public Date getDispositionActionDate(NodeRef record, NodeRef dispositionSchedule, String dispositionActionName)
|
||||||
{
|
{
|
||||||
DispositionSchedule ds = new DispositionScheduleImpl(serviceRegistry, nodeService, dispositionSchedule);
|
DispositionSchedule ds = new DispositionScheduleImpl(serviceRegistry, nodeService, dispositionSchedule);
|
||||||
@@ -1243,7 +1232,8 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void recalculateNextDispositionStep(NodeRef record)
|
public void recalculateNextDispositionStep(NodeRef record)
|
||||||
{
|
{
|
||||||
List<NodeRef> recordFolders = recordFolderService.getRecordFolders(record);
|
List<NodeRef> recordFolders = recordFolderService.getRecordFolders(record);
|
||||||
@@ -1384,14 +1374,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
Date calculatedDate = (nextDispositionActionDate != null ? nextDispositionActionDate : maxDate);
|
Date calculatedDate = (nextDispositionActionDate != null ? nextDispositionActionDate : maxDate);
|
||||||
|
|
||||||
// We only need to update the date if the current one is too early.
|
// We only need to update the date if the current one is too early.
|
||||||
if (recordDate.before(calculatedDate))
|
return recordDate.before(calculatedDate) ? WriteMode.DATE_ONLY : WriteMode.READ_ONLY;
|
||||||
{
|
|
||||||
return WriteMode.DATE_ONLY;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return WriteMode.READ_ONLY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1414,7 +1397,7 @@ public class DispositionServiceImpl extends ServiceBaseImpl
|
|||||||
DispositionSchedule ds = new DispositionScheduleImpl(serviceRegistry, nodeService, folderDS);
|
DispositionSchedule ds = new DispositionScheduleImpl(serviceRegistry, nodeService, folderDS);
|
||||||
List<DispositionActionDefinition> dispositionActionDefinitions = ds.getDispositionActionDefinitions();
|
List<DispositionActionDefinition> dispositionActionDefinitions = ds.getDispositionActionDefinitions();
|
||||||
|
|
||||||
if (dispositionActionDefinitions != null && dispositionActionDefinitions.size() > 0)
|
if (dispositionActionDefinitions != null && !dispositionActionDefinitions.isEmpty())
|
||||||
{
|
{
|
||||||
DispositionActionDefinition firstDispositionActionDef = dispositionActionDefinitions.get(0);
|
DispositionActionDefinition firstDispositionActionDef = dispositionActionDefinitions.get(0);
|
||||||
dispositionNodeRef = folderDS;
|
dispositionNodeRef = folderDS;
|
||||||
|
@@ -34,10 +34,13 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.alfresco.model.ContentModel;
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionActionDefinition;
|
||||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
|
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
|
||||||
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
|
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
|
||||||
|
import org.alfresco.module.org_alfresco_module_rm.event.RecordsManagementEvent;
|
||||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
|
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
|
||||||
import org.alfresco.rest.api.Nodes;
|
import org.alfresco.rest.api.Nodes;
|
||||||
import org.alfresco.rest.api.model.AssocChild;
|
import org.alfresco.rest.api.model.AssocChild;
|
||||||
@@ -61,6 +64,8 @@ import org.alfresco.rm.rest.api.model.UnfiledContainer;
|
|||||||
import org.alfresco.rm.rest.api.model.UnfiledContainerChild;
|
import org.alfresco.rm.rest.api.model.UnfiledContainerChild;
|
||||||
import org.alfresco.rm.rest.api.model.UnfiledRecordFolder;
|
import org.alfresco.rm.rest.api.model.UnfiledRecordFolder;
|
||||||
import org.alfresco.rm.rest.api.model.UnfiledRecordFolderChild;
|
import org.alfresco.rm.rest.api.model.UnfiledRecordFolderChild;
|
||||||
|
import org.alfresco.rm.rest.api.model.RetentionSchedule;
|
||||||
|
import org.alfresco.rm.rest.api.model.RetentionScheduleActionDefinition;
|
||||||
import org.alfresco.service.ServiceRegistry;
|
import org.alfresco.service.ServiceRegistry;
|
||||||
import org.alfresco.service.cmr.model.FileInfo;
|
import org.alfresco.service.cmr.model.FileInfo;
|
||||||
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
@@ -71,6 +76,8 @@ import org.alfresco.service.cmr.security.PersonService;
|
|||||||
import org.alfresco.service.namespace.NamespaceService;
|
import org.alfresco.service.namespace.NamespaceService;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.PROP_COMBINE_DISPOSITION_STEP_CONDITIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class containing Alfresco and RM java services required by the API
|
* Utility class containing Alfresco and RM java services required by the API
|
||||||
* endpoints
|
* endpoints
|
||||||
@@ -891,4 +898,139 @@ public class ApiNodesModelFactory
|
|||||||
mapAssociations(record, info, parameters.getInclude());
|
mapAssociations(record, info, parameters.getInclude());
|
||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Helper method that sets the information for the retention schedule type.
|
||||||
|
* @param dispositionSchedule
|
||||||
|
* @return RetentionSchedule
|
||||||
|
*/
|
||||||
|
public RetentionSchedule mapRetentionScheduleData(DispositionSchedule dispositionSchedule)
|
||||||
|
{
|
||||||
|
RetentionSchedule retentionSchedule = new RetentionSchedule();
|
||||||
|
retentionSchedule.setId(dispositionSchedule.getNodeRef().getId());
|
||||||
|
if(dispositionSchedule.getNodeRef() != null) {
|
||||||
|
NodeRef parent = this.nodeService.getPrimaryParent(dispositionSchedule.getNodeRef()).getParentRef();
|
||||||
|
retentionSchedule.setParentId(parent.getId());
|
||||||
|
}
|
||||||
|
retentionSchedule.setInstructions(dispositionSchedule.getDispositionInstructions());
|
||||||
|
retentionSchedule.setAuthority(dispositionSchedule.getDispositionAuthority());
|
||||||
|
retentionSchedule.setRecordLevel(dispositionSchedule.isRecordLevelDisposition());
|
||||||
|
|
||||||
|
boolean unpublishedUpdates = dispositionSchedule.getDispositionActionDefinitions().stream()
|
||||||
|
.map(DispositionActionDefinition::getNodeRef)
|
||||||
|
.anyMatch(actionDefNodeRef -> nodeService.hasAspect(actionDefNodeRef, RecordsManagementModel.ASPECT_UNPUBLISHED_UPDATE));
|
||||||
|
retentionSchedule.setUnpublishedUpdates(unpublishedUpdates);
|
||||||
|
return retentionSchedule;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that sets the information for the retention schedule action definition type.
|
||||||
|
* @param dispositionActionDefinition
|
||||||
|
* @return RetentionScheduleActionDefinition
|
||||||
|
*/
|
||||||
|
public RetentionScheduleActionDefinition mapRetentionScheduleActionDefData(DispositionActionDefinition dispositionActionDefinition)
|
||||||
|
{
|
||||||
|
RetentionScheduleActionDefinition retentionScheduleActionDefinition = new RetentionScheduleActionDefinition();
|
||||||
|
// Mapping basic properties
|
||||||
|
mapRetentionActionProperties(dispositionActionDefinition, retentionScheduleActionDefinition);
|
||||||
|
// Mapping period and period amount
|
||||||
|
mapPeriodProperties(dispositionActionDefinition, retentionScheduleActionDefinition);
|
||||||
|
// Mapping events properties
|
||||||
|
mapEventsProperties(dispositionActionDefinition, retentionScheduleActionDefinition);
|
||||||
|
return retentionScheduleActionDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that sets core information for the retention schedule action definition type.
|
||||||
|
* @param dispositionActionDefinition
|
||||||
|
* @param retentionScheduleActionDefinition
|
||||||
|
*/
|
||||||
|
private void mapRetentionActionProperties(DispositionActionDefinition dispositionActionDefinition, RetentionScheduleActionDefinition retentionScheduleActionDefinition)
|
||||||
|
{
|
||||||
|
retentionScheduleActionDefinition.setId(dispositionActionDefinition.getId());
|
||||||
|
retentionScheduleActionDefinition.setName(dispositionActionDefinition.getName());
|
||||||
|
retentionScheduleActionDefinition.setDescription(dispositionActionDefinition.getDescription());
|
||||||
|
retentionScheduleActionDefinition.setEligibleOnFirstCompleteEvent(dispositionActionDefinition.eligibleOnFirstCompleteEvent());
|
||||||
|
if (nodeService.getProperty(dispositionActionDefinition.getNodeRef(), PROP_COMBINE_DISPOSITION_STEP_CONDITIONS) != null)
|
||||||
|
{
|
||||||
|
retentionScheduleActionDefinition.setCombineDispositionStepConditions((Boolean) nodeService.getProperty(dispositionActionDefinition.getNodeRef(), PROP_COMBINE_DISPOSITION_STEP_CONDITIONS));
|
||||||
|
}
|
||||||
|
retentionScheduleActionDefinition.setLocation(dispositionActionDefinition.getLocation());
|
||||||
|
if (dispositionActionDefinition.getGhostOnDestroy() != null)
|
||||||
|
{
|
||||||
|
retentionScheduleActionDefinition.setRetainRecordMetadataAfterDestruction(dispositionActionDefinition.getGhostOnDestroy().equals("ghost"));
|
||||||
|
}
|
||||||
|
retentionScheduleActionDefinition.setIndex(dispositionActionDefinition.getIndex());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that sets the period-related information for the retention schedule action definition type.
|
||||||
|
* @param dispositionActionDefinition
|
||||||
|
* @param retentionScheduleActionDefinition
|
||||||
|
*/
|
||||||
|
private void mapPeriodProperties(DispositionActionDefinition dispositionActionDefinition, RetentionScheduleActionDefinition retentionScheduleActionDefinition)
|
||||||
|
{
|
||||||
|
if(dispositionActionDefinition.getPeriodProperty() != null) {
|
||||||
|
retentionScheduleActionDefinition.setPeriodProperty(dispositionActionDefinition.getPeriodProperty().toPrefixString(namespaceService));
|
||||||
|
}
|
||||||
|
String period = dispositionActionDefinition.getPeriod().toString();
|
||||||
|
if (!period.isEmpty())
|
||||||
|
{
|
||||||
|
// In rest api we are splitting `period` property into `period` and `periodAmount`.
|
||||||
|
// so we need to split the period into two properties.
|
||||||
|
// ex. period -> 'month|10' so the split properties would be like below
|
||||||
|
// period -> 'month'
|
||||||
|
// periodAmount -> 10
|
||||||
|
String[] periodArray = period.split("\\|");
|
||||||
|
if (periodArray.length > 0)
|
||||||
|
{
|
||||||
|
retentionScheduleActionDefinition.setPeriod(periodArray[0]);
|
||||||
|
}
|
||||||
|
if (periodArray.length > 1)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
retentionScheduleActionDefinition.setPeriodAmount(Integer.parseInt(periodArray[1]));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
throw new NumberFormatException("Error parsing period amount: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that sets the events information for the retention schedule action definition type.
|
||||||
|
* @param dispositionActionDefinition
|
||||||
|
* @param retentionScheduleActionDefinition
|
||||||
|
*/
|
||||||
|
private void mapEventsProperties(DispositionActionDefinition dispositionActionDefinition, RetentionScheduleActionDefinition retentionScheduleActionDefinition)
|
||||||
|
{
|
||||||
|
List<RecordsManagementEvent> events = dispositionActionDefinition.getEvents();
|
||||||
|
if (events != null && !events.isEmpty())
|
||||||
|
{
|
||||||
|
List<String> eventNames = events.stream()
|
||||||
|
.map(RecordsManagementEvent::getName)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
retentionScheduleActionDefinition.setEvents(eventNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method that sets the optional information for the retention schedule type.
|
||||||
|
* @param retentionSchedule
|
||||||
|
* @param schedule
|
||||||
|
* @param includeParam
|
||||||
|
*/
|
||||||
|
public void mapRetentionScheduleOptionalInfo(RetentionSchedule retentionSchedule, DispositionSchedule schedule, List<String> includeParam)
|
||||||
|
{
|
||||||
|
if (includeParam != null && !includeParam.isEmpty() && includeParam.contains("actions"))
|
||||||
|
{
|
||||||
|
List<RetentionScheduleActionDefinition> actions = schedule.getDispositionActionDefinitions().stream()
|
||||||
|
.map(this::mapRetentionScheduleActionDefData)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
retentionSchedule.setActions(actions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rm.rest.api.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retention schedule
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RetentionSchedule
|
||||||
|
{
|
||||||
|
private String id ;
|
||||||
|
private String parentId;
|
||||||
|
private String authority;
|
||||||
|
private String instructions;
|
||||||
|
private boolean isRecordLevel;
|
||||||
|
private boolean isUnpublishedUpdates;
|
||||||
|
private List<RetentionScheduleActionDefinition> actions;
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rm.rest.api.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retention schedule action definition
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RetentionScheduleActionDefinition
|
||||||
|
{
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private int periodAmount;
|
||||||
|
private String period;
|
||||||
|
private String periodProperty;
|
||||||
|
private boolean combineDispositionStepConditions;
|
||||||
|
private List<String> events;
|
||||||
|
private boolean eligibleOnFirstCompleteEvent;
|
||||||
|
private String description;
|
||||||
|
private boolean retainRecordMetadataAfterDestruction;
|
||||||
|
private String location;
|
||||||
|
private int index;
|
||||||
|
}
|
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.rm.rest.api.retentionschedule;
|
||||||
|
|
||||||
|
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionSchedule;
|
||||||
|
import org.alfresco.module.org_alfresco_module_rm.disposition.DispositionService;
|
||||||
|
import org.alfresco.rest.framework.WebApiDescription;
|
||||||
|
import org.alfresco.rest.framework.resource.RelationshipResource;
|
||||||
|
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction;
|
||||||
|
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
|
||||||
|
import org.alfresco.rest.framework.resource.parameters.Parameters;
|
||||||
|
import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
|
||||||
|
import org.alfresco.rm.rest.api.impl.FilePlanComponentsApiUtils;
|
||||||
|
import org.alfresco.rm.rest.api.model.RetentionSchedule;
|
||||||
|
import org.alfresco.rm.rest.api.recordcategories.RecordCategoriesEntityResource;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
|
import org.alfresco.service.cmr.repository.StoreRef;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.PROP_DISPOSITION_AUTHORITY;
|
||||||
|
import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.PROP_DISPOSITION_INSTRUCTIONS;
|
||||||
|
import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.PROP_RECORD_LEVEL_DISPOSITION;
|
||||||
|
import static org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel.TYPE_RECORD_CATEGORY;
|
||||||
|
import static org.alfresco.module.org_alfresco_module_rm.util.RMParameterCheck.checkNotBlank;
|
||||||
|
import static org.alfresco.util.ParameterCheck.mandatory;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retention schedule relation is used perform retention schedule operation for a record category.
|
||||||
|
*/
|
||||||
|
@RelationshipResource(name = "retention-schedules", entityResource = RecordCategoriesEntityResource.class, title = "Retention Schedule")
|
||||||
|
@Data
|
||||||
|
public class RetentionScheduleRelation implements RelationshipResourceAction.Read<RetentionSchedule>,
|
||||||
|
RelationshipResourceAction.Create<RetentionSchedule>
|
||||||
|
{
|
||||||
|
|
||||||
|
private FilePlanComponentsApiUtils apiUtils;
|
||||||
|
private ApiNodesModelFactory nodesModelFactory;
|
||||||
|
private DispositionService dispositionService;
|
||||||
|
protected NodeService nodeService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@WebApiDescription(title="Create a retention schedule for the particular record category using the 'recordCategoryId'")
|
||||||
|
public List<RetentionSchedule> create(String recordCategoryId, List<RetentionSchedule> nodeInfos, Parameters parameters)
|
||||||
|
{
|
||||||
|
checkNotBlank("recordCategoryId", recordCategoryId);
|
||||||
|
mandatory("entity", nodeInfos);
|
||||||
|
mandatory("parameters", parameters);
|
||||||
|
NodeRef parentNodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, recordCategoryId);
|
||||||
|
List<RetentionSchedule> result = new ArrayList<>();
|
||||||
|
// Create the disposition schedule
|
||||||
|
Map<QName, Serializable> dsProps = new HashMap<>();
|
||||||
|
dsProps.put(PROP_DISPOSITION_AUTHORITY, nodeInfos.get(0).getAuthority());
|
||||||
|
dsProps.put(PROP_DISPOSITION_INSTRUCTIONS, nodeInfos.get(0).getInstructions());
|
||||||
|
dsProps.put(PROP_RECORD_LEVEL_DISPOSITION, nodeInfos.get(0).isRecordLevel());
|
||||||
|
DispositionSchedule dispositionSchedule = dispositionService.createDispositionSchedule(parentNodeRef, dsProps);
|
||||||
|
RetentionSchedule retentionSchedule = nodesModelFactory.mapRetentionScheduleData(dispositionSchedule);
|
||||||
|
result.add(retentionSchedule);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@WebApiDescription(title = "Return a paged list of retention schedule based on the 'recordCategoryId'")
|
||||||
|
public CollectionWithPagingInfo<RetentionSchedule> readAll(String recordCategoryId, Parameters parameters)
|
||||||
|
{
|
||||||
|
checkNotBlank("recordCategoryId", recordCategoryId);
|
||||||
|
mandatory("parameters", parameters);
|
||||||
|
NodeRef parentNodeRef = apiUtils.lookupAndValidateNodeType(recordCategoryId, TYPE_RECORD_CATEGORY);
|
||||||
|
DispositionSchedule schedule = dispositionService.getDispositionSchedule(parentNodeRef);
|
||||||
|
RetentionSchedule retentionSchedule = nodesModelFactory.mapRetentionScheduleData(schedule);
|
||||||
|
List<RetentionSchedule> retentionScheduleList = new ArrayList<>();
|
||||||
|
nodesModelFactory.mapRetentionScheduleOptionalInfo(retentionSchedule, schedule, parameters.getInclude());
|
||||||
|
retentionScheduleList.add(retentionSchedule);
|
||||||
|
return CollectionWithPagingInfo.asPaged(parameters.getPaging(), retentionScheduleList, false,
|
||||||
|
retentionScheduleList.size());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
|
* %%
|
||||||
|
* This file is part of the Alfresco software.
|
||||||
|
* -
|
||||||
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
|
* provided under the following open source license terms:
|
||||||
|
* -
|
||||||
|
* Alfresco is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
* -
|
||||||
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
* -
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package info that defines the Information Governance Retention Schedule REST API
|
||||||
|
*/
|
||||||
|
@WebApi(name="gs", scope=Api.SCOPE.PUBLIC, version=1)
|
||||||
|
package org.alfresco.rm.rest.api.retentionschedule;
|
||||||
|
import org.alfresco.rest.framework.Api;
|
||||||
|
import org.alfresco.rest.framework.WebApi;
|
@@ -51,6 +51,8 @@ import org.alfresco.module.org_alfresco_module_rm.job.publish.PublishExecutorReg
|
|||||||
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
|
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
|
||||||
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
|
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseRMTestCase;
|
||||||
import org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils;
|
import org.alfresco.module.org_alfresco_module_rm.test.util.CommonRMTestUtils;
|
||||||
|
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.alfresco.service.cmr.repository.NodeRef;
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
import org.alfresco.service.namespace.QName;
|
import org.alfresco.service.namespace.QName;
|
||||||
|
|
||||||
@@ -437,19 +439,12 @@ public class DispositionServiceImplTest extends BaseRMTestCase
|
|||||||
|
|
||||||
// Check the disposition schedule
|
// Check the disposition schedule
|
||||||
checkDispositionSchedule(ds, "testCreateDispositionSchedule", "testCreateDispositionSchedule", false);
|
checkDispositionSchedule(ds, "testCreateDispositionSchedule", "testCreateDispositionSchedule", false);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Failure: create disposition schedule on container with existing disposition schedule
|
// Failure: create disposition schedule on container with existing disposition schedule
|
||||||
doTestInTransaction(new FailureTest
|
Assert.assertThrows(ConstraintViolatedException.class,
|
||||||
(
|
() -> {
|
||||||
"Can not create a disposition schedule on a container with an existing disposition schedule"
|
utils.createBasicDispositionSchedule(rmContainer);
|
||||||
)
|
});
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
utils.createBasicDispositionSchedule(rmContainer);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -492,19 +487,12 @@ public class DispositionServiceImplTest extends BaseRMTestCase
|
|||||||
// Check the disposition schedule
|
// Check the disposition schedule
|
||||||
checkDispositionSchedule(testA, "testA", "testA", false);
|
checkDispositionSchedule(testA, "testA", "testA", false);
|
||||||
checkDispositionSchedule(testB, "testB", "testB", false);
|
checkDispositionSchedule(testB, "testB", "testB", false);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Failure: create disposition schedule on container with existing disposition schedule
|
// Failure: create disposition schedule on container with existing disposition schedule
|
||||||
doTestInTransaction(new FailureTest
|
Assert.assertThrows(ConstraintViolatedException.class,
|
||||||
(
|
() -> {
|
||||||
"Can not create a disposition schedule on container with an existing disposition schedule"
|
utils.createBasicDispositionSchedule(rmContainer);
|
||||||
)
|
});
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
utils.createBasicDispositionSchedule(mhContainer11);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Records Management Module
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 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.module.org_alfresco_module_rm.disposition;
|
||||||
|
|
||||||
|
import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel;
|
||||||
|
import org.alfresco.module.org_alfresco_module_rm.test.util.BaseUnitTest;
|
||||||
|
import org.alfresco.rm.rest.api.impl.ApiNodesModelFactory;
|
||||||
|
import org.alfresco.rm.rest.api.model.RetentionSchedule;
|
||||||
|
import org.alfresco.rm.rest.api.model.RetentionScheduleActionDefinition;
|
||||||
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.Period;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retention schedule model unit test
|
||||||
|
*/
|
||||||
|
public class RetentionScheduleModelUnitTest extends BaseUnitTest
|
||||||
|
{
|
||||||
|
private static final String AUTHORITY = "authority";
|
||||||
|
private static final String INSTRUCTIONS = "instructions";
|
||||||
|
private static final String RETAIN_STEP = "retain";
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private ApiNodesModelFactory apiNodesModelFactory;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
DispositionSchedule dispositionSchedule;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
DispositionActionDefinition dispositionActionDefinition;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapRetentionScheduleDataTest()
|
||||||
|
{
|
||||||
|
// Mock data
|
||||||
|
NodeRef nodeRef = generateNodeRef(RecordsManagementModel.TYPE_DISPOSITION_SCHEDULE, true);
|
||||||
|
ChildAssociationRef childAssociationRef = generateChildAssociationRef(filePlan, record);
|
||||||
|
when(dispositionSchedule.getDispositionAuthority()).thenReturn(AUTHORITY);
|
||||||
|
when(dispositionSchedule.getDispositionInstructions()).thenReturn(INSTRUCTIONS);
|
||||||
|
when(dispositionSchedule.getNodeRef()).thenReturn(nodeRef);
|
||||||
|
when(dispositionSchedule.isRecordLevelDisposition()).thenReturn(false);
|
||||||
|
when(mockedNodeService.getPrimaryParent(nodeRef)).thenReturn(childAssociationRef);
|
||||||
|
// Call the method
|
||||||
|
RetentionSchedule expectedResult = apiNodesModelFactory.mapRetentionScheduleData(dispositionSchedule);
|
||||||
|
assertEquals(expectedResult.getId(), dispositionSchedule.getNodeRef().getId());
|
||||||
|
assertEquals(expectedResult.getAuthority(), dispositionSchedule.getDispositionAuthority());
|
||||||
|
assertEquals(expectedResult.getInstructions(), dispositionSchedule.getDispositionInstructions());
|
||||||
|
assertEquals(expectedResult.isRecordLevel(), dispositionSchedule.isRecordLevelDisposition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapRetentionScheduleActionDefDataTest()
|
||||||
|
{
|
||||||
|
// Mock data
|
||||||
|
NodeRef nodeRef = generateNodeRef(RecordsManagementModel.TYPE_DISPOSITION_SCHEDULE, true);
|
||||||
|
String period = "month|10";
|
||||||
|
ChildAssociationRef childAssociationRef = generateChildAssociationRef(filePlan, record);
|
||||||
|
when(dispositionActionDefinition.getNodeRef()).thenReturn(nodeRef);
|
||||||
|
when(dispositionActionDefinition.getName()).thenReturn(RETAIN_STEP);
|
||||||
|
when(dispositionActionDefinition.getDescription()).thenReturn("Description");
|
||||||
|
when(dispositionActionDefinition.getIndex()).thenReturn(1);
|
||||||
|
when(dispositionActionDefinition.getGhostOnDestroy()).thenReturn("ghost");
|
||||||
|
when(dispositionActionDefinition.getPeriod()).thenReturn(new Period(period));
|
||||||
|
when(dispositionActionDefinition.getLocation()).thenReturn("location");
|
||||||
|
when(dispositionActionDefinition.getId()).thenReturn(nodeRef.getId());
|
||||||
|
when(mockedNodeService.getPrimaryParent(nodeRef)).thenReturn(childAssociationRef);
|
||||||
|
// Call the method
|
||||||
|
RetentionScheduleActionDefinition expectedResult = apiNodesModelFactory.mapRetentionScheduleActionDefData(dispositionActionDefinition);
|
||||||
|
String resultPeriod = expectedResult.getPeriod() + "|" + expectedResult.getPeriodAmount();
|
||||||
|
// Assertions
|
||||||
|
assertEquals(expectedResult.getId(), dispositionActionDefinition.getId());
|
||||||
|
assertEquals(expectedResult.getName(), dispositionActionDefinition.getName());
|
||||||
|
assertEquals(expectedResult.getDescription(), dispositionActionDefinition.getDescription());
|
||||||
|
assertEquals(expectedResult.getIndex(), dispositionActionDefinition.getIndex());
|
||||||
|
assertEquals(expectedResult.getLocation(), dispositionActionDefinition.getLocation());
|
||||||
|
assertEquals(new Period(resultPeriod), dispositionActionDefinition.getPeriod());
|
||||||
|
assertTrue(expectedResult.isRetainRecordMetadataAfterDestruction());
|
||||||
|
}
|
||||||
|
}
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
<artifactId>alfresco-governance-services-community-repo-parent</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@@ -40,6 +40,8 @@ tags:
|
|||||||
description: Retrieve and manage unfiled record folders
|
description: Retrieve and manage unfiled record folders
|
||||||
- name: holds
|
- name: holds
|
||||||
description: Retrieve and manage holds
|
description: Retrieve and manage holds
|
||||||
|
- name: retention-schedules
|
||||||
|
description: Perform retention schedule specific operations
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
## GS sites
|
## GS sites
|
||||||
@@ -2634,7 +2636,194 @@ paths:
|
|||||||
description: Unexpected error
|
description: Unexpected error
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Error'
|
$ref: '#/definitions/Error'
|
||||||
|
##retention-schedule
|
||||||
|
'/record-categories/{recordCategoryId}/retention-schedules':
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- retention-schedules
|
||||||
|
summary: Create a retention schedule
|
||||||
|
description: |
|
||||||
|
Create a retention schedule.
|
||||||
|
|
||||||
|
For example, using the following JSON body will create a retention schedule:
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"authority": "Retention Authority",
|
||||||
|
"instructions": "Retention Instructions",
|
||||||
|
"isRecordLevel": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
operationId: createRetentionSchedule
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/recordCategoryIdParam'
|
||||||
|
- in: body
|
||||||
|
name: retentionNodeBodyCreate
|
||||||
|
description: |
|
||||||
|
The retention schedule information to create.
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RetentionNodeBodyCreate'
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Successful response
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RetentionScheduleResponse'
|
||||||
|
'400':
|
||||||
|
description: |
|
||||||
|
Invalid parameter: value of recordCategoryId is invalid
|
||||||
|
'401':
|
||||||
|
description: Authentication failed
|
||||||
|
'403':
|
||||||
|
description: Current user does not have permission to create retention schedule
|
||||||
|
'404':
|
||||||
|
description: recordCategoryId does not exist
|
||||||
|
'409':
|
||||||
|
description: Retention schedule already exist for the given recordCategoryId
|
||||||
|
default:
|
||||||
|
description: Unexpected error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- retention-schedules
|
||||||
|
summary: Get the retention schedule for a record category
|
||||||
|
description: |
|
||||||
|
Get the retention schedule for a record category.
|
||||||
|
|
||||||
|
You can use the **include** parameter (include=actions) to return additional information.
|
||||||
|
|
||||||
|
operationId: getRetentionScheduleList
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/recordCategoryIdParam'
|
||||||
|
- $ref: '#/parameters/retentionScheduleIncludeParam'
|
||||||
|
- $ref: '#/parameters/skipCountParam'
|
||||||
|
- $ref: '#/parameters/maxItemsParam'
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful response
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RetentionScheduleResponseList'
|
||||||
|
'400':
|
||||||
|
description: |
|
||||||
|
Invalid parameter: value of recordCategoryId is invalid
|
||||||
|
'401':
|
||||||
|
description: Authentication failed
|
||||||
|
'403':
|
||||||
|
description: Current user does not have permission to get retention schedule
|
||||||
|
'404':
|
||||||
|
description: recordCategoryId does not exist
|
||||||
|
default:
|
||||||
|
description: Unexpected error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
'/retention-schedules/{retentionScheduleId}/retention-steps':
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- retention-schedules
|
||||||
|
summary: Create a step in the retention schedule
|
||||||
|
description: |
|
||||||
|
Create a step in the retention schedule.
|
||||||
|
|
||||||
|
Order of steps:
|
||||||
|
* "**retain**" or "**cutoff**" should be first
|
||||||
|
* can't use "**cutoff**" after "**transfer**" or "**accession**"
|
||||||
|
* only the "**transfer**" action is allowed multiple times
|
||||||
|
* no steps are allowed after "**destroy**"
|
||||||
|
|
||||||
|
For example, the following JSON body will create a step in the retention schedule:
|
||||||
|
```JSON
|
||||||
|
{
|
||||||
|
"name":"accession",
|
||||||
|
"periodAmount": 2,
|
||||||
|
"period":"month",
|
||||||
|
"periodProperty":"cm:created",
|
||||||
|
"combineDispositionStepConditions": false,
|
||||||
|
"events":["versioned"],
|
||||||
|
"eligibleOnFirstCompleteEvent": true,
|
||||||
|
"description":"Step Description"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
operationId: createRetentionScheduleAction
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/retentionScheduleIdParam'
|
||||||
|
- in: body
|
||||||
|
name: nodeBodyCreate
|
||||||
|
description: |
|
||||||
|
The retention schedule steps information to create.
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RetentionStepNodeBodyCreate'
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Successful response
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RetentionStepNodeBodyResponse'
|
||||||
|
'400':
|
||||||
|
description: |
|
||||||
|
Invalid parameter: value of retentionScheduleId is invalid
|
||||||
|
Invalid parameter (e.g. event, period, periodProperty)
|
||||||
|
'401':
|
||||||
|
description: Authentication failed
|
||||||
|
'403':
|
||||||
|
description: Current user does not have permission to create retention schedule step
|
||||||
|
'404':
|
||||||
|
description: retentionScheduleId does not exist
|
||||||
|
'409':
|
||||||
|
description: |
|
||||||
|
* Invalid Step - Can't use Cut Off after Transfer or Accession
|
||||||
|
* Invalid Step - Destroy action already completed. Can't do any other Action
|
||||||
|
* Invalid Step - This step already exists. You can’t create this step [Transfer action is allowed many times]
|
||||||
|
'422':
|
||||||
|
description: Cut Off or Retain should be the first step
|
||||||
|
default:
|
||||||
|
description: Unexpected error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- retention-schedules
|
||||||
|
summary: Get the list of steps in the retention schedule
|
||||||
|
description: |
|
||||||
|
Get the list of steps in the retention schedule.
|
||||||
|
operationId: getRetentionScheduleActionList
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/parameters/retentionScheduleIdParam'
|
||||||
|
- $ref: '#/parameters/skipCountParam'
|
||||||
|
- $ref: '#/parameters/maxItemsParam'
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful response
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/RetentionStepsNodeBodyResponse'
|
||||||
|
'400':
|
||||||
|
description: |
|
||||||
|
Invalid parameter: value of retentionScheduleId is invalid
|
||||||
|
'401':
|
||||||
|
description: Authentication failed
|
||||||
|
'403':
|
||||||
|
description: Current user does not have permission to get retention schedule steps
|
||||||
|
'404':
|
||||||
|
description: retentionScheduleId does not exist
|
||||||
|
default:
|
||||||
|
description: Unexpected error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/Error'
|
||||||
parameters:
|
parameters:
|
||||||
## File plans
|
## File plans
|
||||||
filePlanEntryIncludeParam:
|
filePlanEntryIncludeParam:
|
||||||
@@ -3100,6 +3289,22 @@ parameters:
|
|||||||
If true, then a name clash will cause an attempt to auto rename by finding a unique name using an integer suffix.
|
If true, then a name clash will cause an attempt to auto rename by finding a unique name using an integer suffix.
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
## RetentionSchedule
|
||||||
|
retentionScheduleIdParam:
|
||||||
|
name: retentionScheduleId
|
||||||
|
in: path
|
||||||
|
description:
|
||||||
|
The identifier of a retention schedule.
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
retentionScheduleIncludeParam:
|
||||||
|
name: include
|
||||||
|
in: query
|
||||||
|
description: |
|
||||||
|
Returns additional information about the retention schedule actions. Any optional field from the response model can be requested. For example:
|
||||||
|
* actions
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
definitions:
|
definitions:
|
||||||
FilePlanComponentBodyUpdate:
|
FilePlanComponentBodyUpdate:
|
||||||
type: object
|
type: object
|
||||||
@@ -4258,6 +4463,225 @@ definitions:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/HoldBulkStatusEntry'
|
$ref: '#/definitions/HoldBulkStatusEntry'
|
||||||
|
RetentionNodeBodyCreate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
authority:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Authority name for the retention schedule.
|
||||||
|
instructions:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Required instructions for the retention schedule.
|
||||||
|
isRecordLevel:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: |
|
||||||
|
This field is used to specify whether the retention schedule needs to be applied in the folder level or record level.
|
||||||
|
True will cause the the retention schedule to apply to records and false will cause the retention schedule to apply to record folders.
|
||||||
|
This cannot be changed once items start being managed by the retention schedule.
|
||||||
|
RetentionScheduleResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
parentId:
|
||||||
|
type: string
|
||||||
|
authority:
|
||||||
|
type: string
|
||||||
|
instructions:
|
||||||
|
type: string
|
||||||
|
isRecordLevel:
|
||||||
|
type: boolean
|
||||||
|
unpublishedUpdates:
|
||||||
|
type: boolean
|
||||||
|
RetentionScheduleResponseList:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
list:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pagination:
|
||||||
|
$ref: '#/definitions/Pagination'
|
||||||
|
entries:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/FullRetentionScheduleResponse'
|
||||||
|
FullRetentionScheduleResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
parentId:
|
||||||
|
type: string
|
||||||
|
authority:
|
||||||
|
type: string
|
||||||
|
instructions:
|
||||||
|
type: string
|
||||||
|
isRecordLevel:
|
||||||
|
type: boolean
|
||||||
|
unpublishedUpdates:
|
||||||
|
type: boolean
|
||||||
|
actions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/Actions'
|
||||||
|
Actions:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
periodAmount:
|
||||||
|
type: integer
|
||||||
|
period:
|
||||||
|
type: string
|
||||||
|
periodProperty:
|
||||||
|
type: string
|
||||||
|
combineDispositionStepConditions:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
eligibleOnFirstCompleteEvent:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
retainRecordMetadataAfterDestruction:
|
||||||
|
type: boolean
|
||||||
|
location:
|
||||||
|
type: string
|
||||||
|
events:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
index:
|
||||||
|
type: integer
|
||||||
|
RetentionStepNodeBodyCreate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
The valid names are:
|
||||||
|
* retain
|
||||||
|
* cutoff
|
||||||
|
* accession
|
||||||
|
* transfer
|
||||||
|
* destroy
|
||||||
|
periodAmount:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
This property is only applicable for the following period values.
|
||||||
|
* day
|
||||||
|
* month
|
||||||
|
* quarter
|
||||||
|
* week
|
||||||
|
* duration
|
||||||
|
* year
|
||||||
|
period:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Valid values for the period.
|
||||||
|
* day = Day
|
||||||
|
* fmend = End Of Financial Month
|
||||||
|
* fqend = End Of Financial Quarter
|
||||||
|
* fyend = End Of Financial Year
|
||||||
|
* immediately = Immediately
|
||||||
|
* monthend = End Of Month
|
||||||
|
* quarterend = End Of Quarter
|
||||||
|
* yearend = End Of Year
|
||||||
|
* month = Month
|
||||||
|
* none = None
|
||||||
|
* quarter = Quarter
|
||||||
|
* week = Week
|
||||||
|
* duration = XML Duration
|
||||||
|
* year = Year
|
||||||
|
|
||||||
|
If you provide XML Duration for the period value, you need to specify a time interval using XML syntax.
|
||||||
|
The syntax should take the form of:
|
||||||
|
P = Period (required)
|
||||||
|
nY = Number of years
|
||||||
|
nM = Number of months
|
||||||
|
nD = Number of days
|
||||||
|
T = Start time of a time section (required if specifying hours, minutes, or seconds)
|
||||||
|
nH = Number of hours
|
||||||
|
nM = Number of minutes
|
||||||
|
nS = Number of seconds
|
||||||
|
For example, ‘P2M10D’ represents two months and ten days.
|
||||||
|
periodProperty:
|
||||||
|
type: string
|
||||||
|
default: cm:created
|
||||||
|
description: |
|
||||||
|
Valid values for the periodProperty property
|
||||||
|
* cm:created = Created Date (defult value)
|
||||||
|
* rma:cutOffDate = Cut Off Date
|
||||||
|
* rma:dispositionAsOf = Retention Action
|
||||||
|
combineDispositionStepConditions:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
This property is only valid for **accession** step.
|
||||||
|
This is used to specify whether to combine the period condition and events for the step execution or only consider one of them.
|
||||||
|
For example:
|
||||||
|
**periodCondition**: After a period of 2 months
|
||||||
|
**eventsCondition**: Case Closed event
|
||||||
|
This flag can be used to consider only (**periodCondition** or **eventsCondition**) or both of them at once.
|
||||||
|
events:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
Valid values for the events property
|
||||||
|
* case_closed = Case Closed
|
||||||
|
* abolished = Abolished
|
||||||
|
* re_designated = Redesignated
|
||||||
|
* no_longer_needed = No Longer Needed
|
||||||
|
* superseded = Superseded
|
||||||
|
* versioned = Versioned
|
||||||
|
* study_complete = Study Complete
|
||||||
|
* training_complete = Training Complete
|
||||||
|
* related_record_trasfered_inactive_storage = Related Record Transferred to Inactive Storage
|
||||||
|
* obsolete = Obsolete
|
||||||
|
* all_allowances_granted_are_terminated = All Allowances Granted are Terminated
|
||||||
|
* WGI_action_complete = WGI Action Complete
|
||||||
|
* separation = Separation
|
||||||
|
* case_complete = Case Complete
|
||||||
|
* declassification_review = Declassification Review
|
||||||
|
eligibleOnFirstCompleteEvent:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
* false = When all events have happened
|
||||||
|
* true = Whichever event is earlier
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
This property is used to provide the step description.
|
||||||
|
retainRecordMetadataAfterDestruction:
|
||||||
|
type: boolean
|
||||||
|
description: |
|
||||||
|
This property is used to retain the metadata after record destruction.
|
||||||
|
location:
|
||||||
|
type: string
|
||||||
|
description: |
|
||||||
|
This property is only valid for transfer step
|
||||||
|
RetentionStepNodeBodyResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
actions:
|
||||||
|
$ref: '#/definitions/Actions'
|
||||||
|
RetentionStepsNodeBodyResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
list:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pagination:
|
||||||
|
$ref: '#/definitions/Pagination'
|
||||||
|
entries:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/RetentionStepNodeBodyResponse'
|
||||||
##
|
##
|
||||||
RequestBodyFile:
|
RequestBodyFile:
|
||||||
type: object
|
type: object
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo</artifactId>
|
<artifactId>alfresco-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-amps</artifactId>
|
<artifactId>alfresco-community-repo-amps</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo</artifactId>
|
<artifactId>alfresco-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo</artifactId>
|
<artifactId>alfresco-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo</artifactId>
|
<artifactId>alfresco-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@@ -9,6 +9,6 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
</project>
|
</project>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo</artifactId>
|
<artifactId>alfresco-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<organization>
|
<organization>
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<developers>
|
<developers>
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<developers>
|
<developers>
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-tests</artifactId>
|
<artifactId>alfresco-community-repo-tests</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<developers>
|
<developers>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo-packaging</artifactId>
|
<artifactId>alfresco-community-repo-packaging</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
2
pom.xml
2
pom.xml
@@ -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">
|
<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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>alfresco-community-repo</artifactId>
|
<artifactId>alfresco-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Alfresco Community Repo Parent</name>
|
<name>Alfresco Community Repo Parent</name>
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo</artifactId>
|
<artifactId>alfresco-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.alfresco</groupId>
|
<groupId>org.alfresco</groupId>
|
||||||
<artifactId>alfresco-community-repo</artifactId>
|
<artifactId>alfresco-community-repo</artifactId>
|
||||||
<version>23.3.0.58-SNAPSHOT</version>
|
<version>23.3.0.61-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@@ -4,30 +4,35 @@
|
|||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
* Copyright (C) 2005 - 2024 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
* the paid license agreement will prevail. Otherwise, the software is
|
* the paid license agreement will prevail. Otherwise, the software is
|
||||||
* provided under the following open source license terms:
|
* provided under the following open source license terms:
|
||||||
*
|
*
|
||||||
* Alfresco is free software: you can redistribute it and/or modify
|
* 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
|
* 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
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* Alfresco is distributed in the hope that it will be useful,
|
* Alfresco is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU Lesser General Public License for more details.
|
* GNU Lesser General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Lesser General Public License
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.authentication.identityservice;
|
package org.alfresco.repo.security.authentication.identityservice;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,6 +40,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||||||
*
|
*
|
||||||
* @author Gavin Cornwell
|
* @author Gavin Cornwell
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("PMD.ExcessivePublicCount")
|
||||||
public class IdentityServiceConfig
|
public class IdentityServiceConfig
|
||||||
{
|
{
|
||||||
private static final String REALMS = "realms";
|
private static final String REALMS = "realms";
|
||||||
@@ -62,6 +68,7 @@ public class IdentityServiceConfig
|
|||||||
private String principalAttribute;
|
private String principalAttribute;
|
||||||
private boolean clientIdValidationDisabled;
|
private boolean clientIdValidationDisabled;
|
||||||
private String adminConsoleRedirectPath;
|
private String adminConsoleRedirectPath;
|
||||||
|
private String signatureAlgorithms;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -306,4 +313,18 @@ public class IdentityServiceConfig
|
|||||||
{
|
{
|
||||||
this.adminConsoleRedirectPath = adminConsoleRedirectPath;
|
this.adminConsoleRedirectPath = adminConsoleRedirectPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<SignatureAlgorithm> getSignatureAlgorithms()
|
||||||
|
{
|
||||||
|
return Stream.of(signatureAlgorithms.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.map(SignatureAlgorithm::from)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignatureAlgorithms(String signatureAlgorithms)
|
||||||
|
{
|
||||||
|
this.signatureAlgorithms = signatureAlgorithms;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -58,10 +58,13 @@ import java.util.function.Function;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.JOSEObjectType;
|
||||||
import com.nimbusds.jose.JWSAlgorithm;
|
import com.nimbusds.jose.JWSAlgorithm;
|
||||||
import com.nimbusds.jose.jwk.source.DefaultJWKSetCache;
|
import com.nimbusds.jose.jwk.source.DefaultJWKSetCache;
|
||||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||||
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
|
import com.nimbusds.jose.jwk.source.RemoteJWKSet;
|
||||||
|
import com.nimbusds.jose.proc.BadJOSEException;
|
||||||
|
import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier;
|
||||||
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
|
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
|
||||||
import com.nimbusds.jose.proc.SecurityContext;
|
import com.nimbusds.jose.proc.SecurityContext;
|
||||||
import com.nimbusds.jose.util.ResourceRetriever;
|
import com.nimbusds.jose.util.ResourceRetriever;
|
||||||
@@ -129,6 +132,9 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||||||
public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentityServiceFacade>
|
public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentityServiceFacade>
|
||||||
{
|
{
|
||||||
private static final Log LOGGER = LogFactory.getLog(IdentityServiceFacadeFactoryBean.class);
|
private static final Log LOGGER = LogFactory.getLog(IdentityServiceFacadeFactoryBean.class);
|
||||||
|
|
||||||
|
private static final JOSEObjectType AT_JWT = new JOSEObjectType("at+jwt");
|
||||||
|
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private SpringBasedIdentityServiceFacadeFactory factory;
|
private SpringBasedIdentityServiceFacadeFactory factory;
|
||||||
|
|
||||||
@@ -554,12 +560,20 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
|
|||||||
|
|
||||||
static class JwtDecoderProvider
|
static class JwtDecoderProvider
|
||||||
{
|
{
|
||||||
private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.RS256;
|
private static final SignatureAlgorithm DEFAULT_SIGNATURE_ALGORITHM = SignatureAlgorithm.RS256;
|
||||||
private final IdentityServiceConfig config;
|
private final IdentityServiceConfig config;
|
||||||
|
private final Set<SignatureAlgorithm> signatureAlgorithms;
|
||||||
|
|
||||||
JwtDecoderProvider(IdentityServiceConfig config)
|
JwtDecoderProvider(IdentityServiceConfig config)
|
||||||
{
|
{
|
||||||
this.config = requireNonNull(config);
|
this.config = requireNonNull(config);
|
||||||
|
this.signatureAlgorithms = ofNullable(config.getSignatureAlgorithms())
|
||||||
|
.filter(not(Set::isEmpty))
|
||||||
|
.orElseGet(() -> {
|
||||||
|
LOGGER.warn("Unable to find any valid signature algorithms in the configuration. "
|
||||||
|
+ "Using the default signature algorithm: " + DEFAULT_SIGNATURE_ALGORITHM.getName() + ".");
|
||||||
|
return Set.of(DEFAULT_SIGNATURE_ALGORITHM);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public JwtDecoder createJwtDecoder(RestOperations rest, ProviderDetails providerDetails)
|
public JwtDecoder createJwtDecoder(RestOperations rest, ProviderDetails providerDetails)
|
||||||
@@ -587,13 +601,13 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
|
|||||||
{
|
{
|
||||||
final RSAPublicKey publicKey = parsePublicKey(config.getRealmKey());
|
final RSAPublicKey publicKey = parsePublicKey(config.getRealmKey());
|
||||||
return NimbusJwtDecoder.withPublicKey(publicKey)
|
return NimbusJwtDecoder.withPublicKey(publicKey)
|
||||||
.signatureAlgorithm(SIGNATURE_ALGORITHM)
|
.signatureAlgorithm(DEFAULT_SIGNATURE_ALGORITHM)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
final String jwkSetUri = requireValidJwkSetUri(providerDetails);
|
final String jwkSetUri = requireValidJwkSetUri(providerDetails);
|
||||||
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri)
|
final NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder decoderBuilder = NimbusJwtDecoder.withJwkSetUri(jwkSetUri);
|
||||||
.jwsAlgorithm(SIGNATURE_ALGORITHM)
|
signatureAlgorithms.forEach(decoderBuilder::jwsAlgorithm);
|
||||||
|
return decoderBuilder
|
||||||
.restOperations(rest)
|
.restOperations(rest)
|
||||||
.jwtProcessorCustomizer(this::reconfigureJWKSCache)
|
.jwtProcessorCustomizer(this::reconfigureJWKSCache)
|
||||||
.build();
|
.build();
|
||||||
@@ -633,8 +647,11 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
|
|||||||
resourceRetriever.get(), cache);
|
resourceRetriever.get(), cache);
|
||||||
|
|
||||||
jwtProcessor.setJWSKeySelector(new JWSVerificationKeySelector<>(
|
jwtProcessor.setJWSKeySelector(new JWSVerificationKeySelector<>(
|
||||||
JWSAlgorithm.parse(SIGNATURE_ALGORITHM.getName()),
|
signatureAlgorithms.stream()
|
||||||
|
.map(signatureAlgorithm -> JWSAlgorithm.parse(signatureAlgorithm.getName()))
|
||||||
|
.collect(Collectors.toSet()),
|
||||||
cachingJWKSource));
|
cachingJWKSource));
|
||||||
|
jwtProcessor.setJWSTypeVerifier(new CustomJOSEObjectTypeVerifier(JOSEObjectType.JWT, AT_JWT));
|
||||||
}
|
}
|
||||||
|
|
||||||
private OAuth2TokenValidator<Jwt> createJwtTokenValidator(ProviderDetails providerDetails)
|
private OAuth2TokenValidator<Jwt> createJwtTokenValidator(ProviderDetails providerDetails)
|
||||||
@@ -759,7 +776,6 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static class CustomClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory
|
static class CustomClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory
|
||||||
{
|
{
|
||||||
CustomClientHttpRequestFactory(HttpClient httpClient)
|
CustomClientHttpRequestFactory(HttpClient httpClient)
|
||||||
@@ -781,9 +797,22 @@ public class IdentityServiceFacadeFactoryBean implements FactoryBean<IdentitySer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class CustomJOSEObjectTypeVerifier extends DefaultJOSEObjectTypeVerifier<SecurityContext>
|
||||||
|
{
|
||||||
|
public CustomJOSEObjectTypeVerifier(JOSEObjectType... allowedTypes)
|
||||||
|
{
|
||||||
|
super(Set.of(allowedTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify(JOSEObjectType type, SecurityContext context) throws BadJOSEException
|
||||||
|
{
|
||||||
|
super.verify(type, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isDefined(String value)
|
private static boolean isDefined(String value)
|
||||||
{
|
{
|
||||||
return value != null && !value.isBlank();
|
return value != null && !value.isBlank();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
view:childName="app:messages">
|
view:childName="app:messages">
|
||||||
<view:aspects>
|
<view:aspects>
|
||||||
<app:uifacets></app:uifacets>
|
<app:uifacets></app:uifacets>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
</view:aspects>
|
</view:aspects>
|
||||||
<view:properties>
|
<view:properties>
|
||||||
<cm:description>${spaces.messages.description}</cm:description>
|
<cm:description>${spaces.messages.description}</cm:description>
|
||||||
|
Binary file not shown.
@@ -4,6 +4,8 @@
|
|||||||
view:childName="app:webclient_extension">
|
view:childName="app:webclient_extension">
|
||||||
<view:aspects>
|
<view:aspects>
|
||||||
<app:uifacets></app:uifacets>
|
<app:uifacets></app:uifacets>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
</view:aspects>
|
</view:aspects>
|
||||||
<view:properties>
|
<view:properties>
|
||||||
<cm:description>${spaces.web.client.extension.description}</cm:description>
|
<cm:description>${spaces.web.client.extension.description}</cm:description>
|
||||||
|
Binary file not shown.
Binary file not shown.
@@ -12,6 +12,10 @@
|
|||||||
<cm:title>${spaces.rendition.rendering_actions.name}</cm:title>
|
<cm:title>${spaces.rendition.rendering_actions.name}</cm:title>
|
||||||
<cm:description>${spaces.rendition.rendering_actions.description}</cm:description>
|
<cm:description>${spaces.rendition.rendering_actions.description}</cm:description>
|
||||||
</view:properties>
|
</view:properties>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
|
|
||||||
</view:view>
|
</view:view>
|
||||||
|
@@ -12,6 +12,10 @@
|
|||||||
<cm:title>${spaces.replication.replication_actions.name}</cm:title>
|
<cm:title>${spaces.replication.replication_actions.name}</cm:title>
|
||||||
<cm:description>${spaces.replication.replication_actions.description}</cm:description>
|
<cm:description>${spaces.replication.replication_actions.description}</cm:description>
|
||||||
</view:properties>
|
</view:properties>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
|
|
||||||
</view:view>
|
</view:view>
|
||||||
|
@@ -11,5 +11,9 @@
|
|||||||
<cm:title>${spaces.actions.scheduled_actions.name}</cm:title>
|
<cm:title>${spaces.actions.scheduled_actions.name}</cm:title>
|
||||||
<cm:description>${spaces.actions.scheduled_actions.description}</cm:description>
|
<cm:description>${spaces.actions.scheduled_actions.description}</cm:description>
|
||||||
</view:properties>
|
</view:properties>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
</view:view>
|
</view:view>
|
||||||
|
@@ -14,5 +14,9 @@
|
|||||||
<cm:description>${spaces.solr_facets.root.description}
|
<cm:description>${spaces.solr_facets.root.description}
|
||||||
</cm:description>
|
</cm:description>
|
||||||
</view:properties>
|
</view:properties>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</srft:facets>
|
</srft:facets>
|
||||||
</view:view>
|
</view:view>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<view:view xmlns:view="http://www.alfresco.org/view/repository/1.0"
|
<view:view xmlns:view="http://www.alfresco.org/view/repository/1.0"
|
||||||
xmlns:cm="http://www.alfresco.org/model/content/1.0"
|
xmlns:cm="http://www.alfresco.org/model/content/1.0"
|
||||||
xmlns:app="http://www.alfresco.org/model/application/1.0"
|
xmlns:app="http://www.alfresco.org/model/application/1.0"
|
||||||
xmlns:emailserver="http://www.alfresco.org/model/emailserver/1.0">
|
xmlns:emailserver="http://www.alfresco.org/model/emailserver/1.0" xmlns:sys="http://www.alfresco.org/model/system/1.0">
|
||||||
|
|
||||||
<!-- NOTE: all replaced properties referenced from repository.properties file must also be
|
<!-- NOTE: all replaced properties referenced from repository.properties file must also be
|
||||||
mapped in the import-export-context.xml spacesStoreImporter/configuration section -->
|
mapped in the import-export-context.xml spacesStoreImporter/configuration section -->
|
||||||
@@ -30,6 +30,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.dictionary.name}</cm:title>
|
<cm:title>${spaces.dictionary.name}</cm:title>
|
||||||
<cm:description>${spaces.dictionary.description}</cm:description>
|
<cm:description>${spaces.dictionary.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
<cm:contains>
|
<cm:contains>
|
||||||
<cm:folder view:childName="${spaces.templates.childname}">
|
<cm:folder view:childName="${spaces.templates.childname}">
|
||||||
<app:uifacets/>
|
<app:uifacets/>
|
||||||
@@ -37,6 +41,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.templates.name}</cm:title>
|
<cm:title>${spaces.templates.name}</cm:title>
|
||||||
<cm:description>${spaces.templates.description}</cm:description>
|
<cm:description>${spaces.templates.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
<cm:folder
|
<cm:folder
|
||||||
view:childName="${spaces.templates.content.childname}">
|
view:childName="${spaces.templates.content.childname}">
|
||||||
@@ -45,6 +53,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.templates.content.name}</cm:title>
|
<cm:title>${spaces.templates.content.name}</cm:title>
|
||||||
<cm:description>${spaces.templates.content.description}</cm:description>
|
<cm:description>${spaces.templates.content.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
<cm:folder
|
<cm:folder
|
||||||
view:childName="${spaces.templates.email.childname}">
|
view:childName="${spaces.templates.email.childname}">
|
||||||
@@ -53,6 +65,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.templates.email.name}</cm:title>
|
<cm:title>${spaces.templates.email.name}</cm:title>
|
||||||
<cm:description>${spaces.templates.email.description}</cm:description>
|
<cm:description>${spaces.templates.email.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
<cm:contains>
|
<cm:contains>
|
||||||
<cm:folder
|
<cm:folder
|
||||||
view:childName="${spaces.templates.email.invite.childname}">
|
view:childName="${spaces.templates.email.invite.childname}">
|
||||||
@@ -79,6 +95,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.templates.rss.name}</cm:title>
|
<cm:title>${spaces.templates.rss.name}</cm:title>
|
||||||
<cm:description>${spaces.templates.rss.description}</cm:description>
|
<cm:description>${spaces.templates.rss.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
<cm:folder
|
<cm:folder
|
||||||
view:childName="${spaces.savedsearches.childname}">
|
view:childName="${spaces.savedsearches.childname}">
|
||||||
@@ -93,6 +113,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.savedsearches.name}</cm:title>
|
<cm:title>${spaces.savedsearches.name}</cm:title>
|
||||||
<cm:description>${spaces.savedsearches.description}</cm:description>
|
<cm:description>${spaces.savedsearches.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
<cm:folder view:childName="${spaces.scripts.childname}">
|
<cm:folder view:childName="${spaces.scripts.childname}">
|
||||||
<app:uifacets/>
|
<app:uifacets/>
|
||||||
@@ -100,6 +124,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.scripts.name}</cm:title>
|
<cm:title>${spaces.scripts.name}</cm:title>
|
||||||
<cm:description>${spaces.scripts.description}</cm:description>
|
<cm:description>${spaces.scripts.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
<cm:folder view:childName="${spaces.nodetemplates.childname}">
|
<cm:folder view:childName="${spaces.nodetemplates.childname}">
|
||||||
<app:uifacets/>
|
<app:uifacets/>
|
||||||
@@ -107,6 +135,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.nodeTemplatesSpace.name}</cm:title>
|
<cm:title>${spaces.nodeTemplatesSpace.name}</cm:title>
|
||||||
<cm:description>${spaces.nodeTemplatesSpace.description}</cm:description>
|
<cm:description>${spaces.nodeTemplatesSpace.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
<cm:folder view:childName="${spaces.smartfolders.childname}">
|
<cm:folder view:childName="${spaces.smartfolders.childname}">
|
||||||
<view:acl view:inherit="false">
|
<view:acl view:inherit="false">
|
||||||
@@ -120,6 +152,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.smartfoldertemplates.name}</cm:title>
|
<cm:title>${spaces.smartfoldertemplates.name}</cm:title>
|
||||||
<cm:description>${spaces.smartfoldertemplates.description}</cm:description>
|
<cm:description>${spaces.smartfoldertemplates.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
<cm:folder view:childName="${spaces.smartdownloads.childname}">
|
<cm:folder view:childName="${spaces.smartdownloads.childname}">
|
||||||
<view:acl view:inherit="false">
|
<view:acl view:inherit="false">
|
||||||
@@ -133,6 +169,10 @@
|
|||||||
<app:icon>space-icon-default</app:icon>
|
<app:icon>space-icon-default</app:icon>
|
||||||
<cm:title>${spaces.smartdownloads.name}</cm:title>
|
<cm:title>${spaces.smartdownloads.name}</cm:title>
|
||||||
<cm:description>${spaces.smartdownloads.description}</cm:description>
|
<cm:description>${spaces.smartdownloads.description}</cm:description>
|
||||||
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
|
</view:aspects>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
</cm:contains>
|
</cm:contains>
|
||||||
</cm:folder>
|
</cm:folder>
|
||||||
|
@@ -4,10 +4,13 @@
|
|||||||
xmlns:view="http://www.alfresco.org/view/repository/1.0"
|
xmlns:view="http://www.alfresco.org/view/repository/1.0"
|
||||||
xmlns:cm="http://www.alfresco.org/model/content/1.0"
|
xmlns:cm="http://www.alfresco.org/model/content/1.0"
|
||||||
xmlns:app="http://www.alfresco.org/model/application/1.0"
|
xmlns:app="http://www.alfresco.org/model/application/1.0"
|
||||||
xmlns:trx="http://www.alfresco.org/model/transfer/1.0">
|
xmlns:trx="http://www.alfresco.org/model/transfer/1.0"
|
||||||
|
xmlns:sys="http://www.alfresco.org/model/system/1.0">
|
||||||
|
|
||||||
<cm:folder view:childName="${spaces.transfers.childname}">
|
<cm:folder view:childName="${spaces.transfers.childname}">
|
||||||
<view:aspects>
|
<view:aspects>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
</view:aspects>
|
</view:aspects>
|
||||||
<view:properties>
|
<view:properties>
|
||||||
<cm:description>${spaces.transfers.description}</cm:description>
|
<cm:description>${spaces.transfers.description}</cm:description>
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
<cm:folder xmlns:alf="http://www.alfresco.org" xmlns:d="http://www.alfresco.org/model/dictionary/1.0" xmlns:view="http://www.alfresco.org/view/repository/1.0" xmlns:act="http://www.alfresco.org/model/action/1.0" xmlns:wf="http://www.alfresco.org/model/workflow/1.0" xmlns:app="http://www.alfresco.org/model/application/1.0" xmlns:ver="http://www.alfresco.org/model/versionstore/1.0" xmlns:usr="http://www.alfresco.org/model/user/1.0" xmlns:cm="http://www.alfresco.org/model/content/1.0" xmlns:sys="http://www.alfresco.org/model/system/1.0" xmlns:rule="http://www.alfresco.org/model/rule/1.0" xmlns:fm="http://www.alfresco.org/model/forum/1.0" xmlns:bpm="http://www.alfresco.org/model/bpm/1.0" xmlns:custom="custom.model" xmlns="" view:childName="cm:webscripts">
|
<cm:folder xmlns:alf="http://www.alfresco.org" xmlns:d="http://www.alfresco.org/model/dictionary/1.0" xmlns:view="http://www.alfresco.org/view/repository/1.0" xmlns:act="http://www.alfresco.org/model/action/1.0" xmlns:wf="http://www.alfresco.org/model/workflow/1.0" xmlns:app="http://www.alfresco.org/model/application/1.0" xmlns:ver="http://www.alfresco.org/model/versionstore/1.0" xmlns:usr="http://www.alfresco.org/model/user/1.0" xmlns:cm="http://www.alfresco.org/model/content/1.0" xmlns:sys="http://www.alfresco.org/model/system/1.0" xmlns:rule="http://www.alfresco.org/model/rule/1.0" xmlns:fm="http://www.alfresco.org/model/forum/1.0" xmlns:bpm="http://www.alfresco.org/model/bpm/1.0" xmlns:custom="custom.model" xmlns="" view:childName="cm:webscripts">
|
||||||
<view:aspects>
|
<view:aspects>
|
||||||
<app:uifacets></app:uifacets>
|
<app:uifacets></app:uifacets>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
</view:aspects>
|
</view:aspects>
|
||||||
<view:properties>
|
<view:properties>
|
||||||
<cm:description>${webscripts.url_addressable_web_services}</cm:description>
|
<cm:description>${webscripts.url_addressable_web_services}</cm:description>
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
<cm:folder xmlns:alf="http://www.alfresco.org" xmlns:d="http://www.alfresco.org/model/dictionary/1.0" xmlns:view="http://www.alfresco.org/view/repository/1.0" xmlns:act="http://www.alfresco.org/model/action/1.0" xmlns:wf="http://www.alfresco.org/model/workflow/1.0" xmlns:app="http://www.alfresco.org/model/application/1.0" xmlns:ver="http://www.alfresco.org/model/versionstore/1.0" xmlns:usr="http://www.alfresco.org/model/user/1.0" xmlns:cm="http://www.alfresco.org/model/content/1.0" xmlns:sys="http://www.alfresco.org/model/system/1.0" xmlns:rule="http://www.alfresco.org/model/rule/1.0" xmlns:fm="http://www.alfresco.org/model/forum/1.0" xmlns:bpm="http://www.alfresco.org/model/bpm/1.0" xmlns:custom="custom.model" xmlns="" view:childName="cm:extensionwebscripts">
|
<cm:folder xmlns:alf="http://www.alfresco.org" xmlns:d="http://www.alfresco.org/model/dictionary/1.0" xmlns:view="http://www.alfresco.org/view/repository/1.0" xmlns:act="http://www.alfresco.org/model/action/1.0" xmlns:wf="http://www.alfresco.org/model/workflow/1.0" xmlns:app="http://www.alfresco.org/model/application/1.0" xmlns:ver="http://www.alfresco.org/model/versionstore/1.0" xmlns:usr="http://www.alfresco.org/model/user/1.0" xmlns:cm="http://www.alfresco.org/model/content/1.0" xmlns:sys="http://www.alfresco.org/model/system/1.0" xmlns:rule="http://www.alfresco.org/model/rule/1.0" xmlns:fm="http://www.alfresco.org/model/forum/1.0" xmlns:bpm="http://www.alfresco.org/model/bpm/1.0" xmlns:custom="custom.model" xmlns="" view:childName="cm:extensionwebscripts">
|
||||||
<view:aspects>
|
<view:aspects>
|
||||||
<app:uifacets></app:uifacets>
|
<app:uifacets></app:uifacets>
|
||||||
|
<sys:undeletable/>
|
||||||
|
<sys:unmovable/>
|
||||||
</view:aspects>
|
</view:aspects>
|
||||||
<view:properties>
|
<view:properties>
|
||||||
<cm:description>${webscriptsextentions.customized_web_scripts}</cm:description>
|
<cm:description>${webscriptsextentions.customized_web_scripts}</cm:description>
|
||||||
|
@@ -68,14 +68,6 @@
|
|||||||
<property name="systemPaths">
|
<property name="systemPaths">
|
||||||
<list>
|
<list>
|
||||||
<value>/${spaces.company_home.childname}</value>
|
<value>/${spaces.company_home.childname}</value>
|
||||||
<value>/${spaces.company_home.childname}/${spaces.dictionary.childname}</value>
|
|
||||||
<value>/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.childname}</value>
|
|
||||||
<value>/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.content.childname}</value>
|
|
||||||
<value>/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}</value>
|
|
||||||
<value>/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.rss.childname}</value>
|
|
||||||
<value>/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.scripts.childname}</value>
|
|
||||||
<value>/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.webscripts.childname}</value>
|
|
||||||
<value>/${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.extension_webscripts.childname}</value>
|
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
@@ -155,6 +155,9 @@
|
|||||||
<property name="adminConsoleRedirectPath">
|
<property name="adminConsoleRedirectPath">
|
||||||
<value>${identity-service.admin-console.redirect-path}</value>
|
<value>${identity-service.admin-console.redirect-path}</value>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="signatureAlgorithms">
|
||||||
|
<value>${identity-service.signature-algorithms:RS256,PS256}</value>
|
||||||
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- Enable control over mapping between request and user ID -->
|
<!-- Enable control over mapping between request and user ID -->
|
||||||
|
@@ -11,4 +11,5 @@ identity-service.realm=alfresco
|
|||||||
identity-service.resource=alfresco
|
identity-service.resource=alfresco
|
||||||
identity-service.credentials.secret=
|
identity-service.credentials.secret=
|
||||||
identity-service.public-client=true
|
identity-service.public-client=true
|
||||||
identity-service.admin-console.redirect-path=/alfresco/s/admin/admin-communitysummary
|
identity-service.admin-console.redirect-path=/alfresco/s/admin/admin-communitysummary
|
||||||
|
identity-service.signature-algorithms=RS256,PS256
|
@@ -77,8 +77,7 @@ import org.junit.runners.Suite;
|
|||||||
org.alfresco.repo.activities.feed.cleanup.FeedCleanerTestCaseSensitivity.class,
|
org.alfresco.repo.activities.feed.cleanup.FeedCleanerTestCaseSensitivity.class,
|
||||||
org.alfresco.repo.activities.SiteActivityTestCaseInsensitivity.class,
|
org.alfresco.repo.activities.SiteActivityTestCaseInsensitivity.class,
|
||||||
org.alfresco.repo.admin.registry.RegistryServiceImplTest.class,
|
org.alfresco.repo.admin.registry.RegistryServiceImplTest.class,
|
||||||
org.alfresco.repo.bootstrap.DataDictionaryFolderTest.class,
|
org.alfresco.repo.bootstrap.DataDictionaryFolderTest.class
|
||||||
org.alfresco.repo.action.executer.NodeSizeActionExecuterTest.class
|
|
||||||
})
|
})
|
||||||
public class AppContext01TestSuite
|
public class AppContext01TestSuite
|
||||||
{
|
{
|
||||||
|
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2024 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.bootstrap;
|
||||||
|
|
||||||
|
|
||||||
|
import org.alfresco.model.ContentModel;
|
||||||
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
|
import org.alfresco.service.ServiceRegistry;
|
||||||
|
import org.alfresco.service.cmr.repository.ChildAssociationRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeRef;
|
||||||
|
import org.alfresco.service.cmr.repository.NodeService;
|
||||||
|
import org.alfresco.service.namespace.QName;
|
||||||
|
import org.alfresco.util.BaseSpringTest;
|
||||||
|
import org.alfresco.util.test.junitrules.ApplicationContextInit;
|
||||||
|
import org.alfresco.util.test.junitrules.WellKnownNodes;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DataDictionaryFolderTest extends BaseSpringTest
|
||||||
|
{
|
||||||
|
@ClassRule
|
||||||
|
private static final ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit();
|
||||||
|
|
||||||
|
private static final String DATA_DICTIONARY = "Data Dictionary";
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
private WellKnownNodes wellKnownNodes = new WellKnownNodes(APP_CONTEXT_INIT);
|
||||||
|
|
||||||
|
private NodeService nodeService;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before()
|
||||||
|
{
|
||||||
|
ServiceRegistry serviceRegistry = (ServiceRegistry) this.applicationContext.getBean("ServiceRegistry");
|
||||||
|
this.nodeService = serviceRegistry.getNodeService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDataDictionaryFolderIsUndeletable()
|
||||||
|
{
|
||||||
|
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
|
||||||
|
// get the company_home
|
||||||
|
NodeRef companyHomeRef = wellKnownNodes.getCompanyHome();
|
||||||
|
// get the Data Dictionary
|
||||||
|
NodeRef dataDictionaryRef = nodeService.getChildByName(companyHomeRef, ContentModel.ASSOC_CONTAINS, DATA_DICTIONARY);
|
||||||
|
assertTrue(nodeService.hasAspect(dataDictionaryRef, ContentModel.ASPECT_UNDELETABLE));
|
||||||
|
|
||||||
|
List<ChildAssociationRef> chilAssocsList = nodeService.getChildAssocs(dataDictionaryRef);
|
||||||
|
|
||||||
|
chilAssocsList.stream()
|
||||||
|
.map(ChildAssociationRef::getChildRef)
|
||||||
|
.forEach(childNodeRef -> {
|
||||||
|
assertTrue(nodeService.hasAspect(childNodeRef, ContentModel.ASPECT_UNDELETABLE));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nodeService.deleteNode(childNodeRef);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
assertTrue(ex.getMessage().contains("deletion is not allowed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDataDictionaryFolderIsUnmovable()
|
||||||
|
{
|
||||||
|
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
|
||||||
|
// get the company_home
|
||||||
|
NodeRef companyHomeRef = wellKnownNodes.getCompanyHome();
|
||||||
|
// get the Data Dictionary
|
||||||
|
NodeRef dataDictionaryRef = nodeService.getChildByName(companyHomeRef, ContentModel.ASSOC_CONTAINS, DATA_DICTIONARY);
|
||||||
|
assertTrue(nodeService.hasAspect(dataDictionaryRef, ContentModel.ASPECT_UNMOVABLE));
|
||||||
|
|
||||||
|
List<ChildAssociationRef> chilAssocsList = nodeService.getChildAssocs(dataDictionaryRef);
|
||||||
|
|
||||||
|
chilAssocsList.stream()
|
||||||
|
.map(ChildAssociationRef::getChildRef)
|
||||||
|
.forEach(childNodeRef -> {
|
||||||
|
assertTrue(nodeService.hasAspect(childNodeRef, ContentModel.ASPECT_UNMOVABLE));
|
||||||
|
NodeRef folderRef = nodeService.createNode(
|
||||||
|
companyHomeRef,
|
||||||
|
ContentModel.ASSOC_CONTAINS,
|
||||||
|
QName.createQName("testDeleteAndRestore-folder2-" + System.currentTimeMillis()),
|
||||||
|
ContentModel.TYPE_FOLDER
|
||||||
|
).getChildRef();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nodeService.moveNode(childNodeRef, folderRef, ContentModel.ASSOC_CONTAINS, ContentModel.ASSOC_CONTAINS);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
assertTrue(ex.getMessage().contains("move is not allowed"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -25,53 +25,156 @@
|
|||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.authentication.identityservice;
|
package org.alfresco.repo.security.authentication.identityservice;
|
||||||
|
|
||||||
|
import static com.nimbusds.jose.HeaderParameterNames.KEY_ID;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.nimbusds.jose.Algorithm;
|
||||||
|
import com.nimbusds.jose.JOSEException;
|
||||||
|
import com.nimbusds.jose.JOSEObjectType;
|
||||||
|
import com.nimbusds.jose.JWSAlgorithm;
|
||||||
|
import com.nimbusds.jose.JWSHeader;
|
||||||
|
import com.nimbusds.jose.crypto.RSASSASigner;
|
||||||
|
import com.nimbusds.jose.jwk.KeyUse;
|
||||||
|
import com.nimbusds.jose.jwk.RSAKey;
|
||||||
|
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
|
||||||
|
import com.nimbusds.jwt.JWTClaimsSet;
|
||||||
|
import com.nimbusds.jwt.SignedJWT;
|
||||||
import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
|
import com.nimbusds.openid.connect.sdk.claims.PersonClaims;
|
||||||
|
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtAudienceValidator;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtAudienceValidator;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtDecoderProvider;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtDecoderProvider;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtIssuerValidator;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtIssuerValidator;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
|
import org.springframework.security.oauth2.jwt.BadJwtException;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.web.client.RestOperations;
|
||||||
|
|
||||||
public class IdentityServiceFacadeFactoryBeanTest
|
public class IdentityServiceFacadeFactoryBeanTest
|
||||||
{
|
{
|
||||||
private static final String EXPECTED_ISSUER = "expected-issuer";
|
private static final String EXPECTED_ISSUER = "expected-issuer";
|
||||||
private static final String EXPECTED_AUDIENCE = "expected-audience";
|
private static final String EXPECTED_AUDIENCE = "expected-audience";
|
||||||
|
public final IdentityServiceConfig config = mock(IdentityServiceConfig.class);
|
||||||
|
public final RestOperations restOperations = mock(RestOperations.class);
|
||||||
|
public final ResponseEntity responseEntity = mock(ResponseEntity.class);
|
||||||
|
public final ProviderDetails providerDetails = mock(ProviderDetails.class);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreateJwtDecoderWithoutIDSWhenPublicKeyIsProvided()
|
public void shouldCreateJwtDecoderWithoutIDSWhenPublicKeyIsProvided()
|
||||||
{
|
{
|
||||||
final IdentityServiceConfig config = mock(IdentityServiceConfig.class);
|
when(config.getRealmKey()).thenReturn(
|
||||||
when(config.getRealmKey()).thenReturn("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAve3MabX/rp3LbE7/zNqKxuid8WT7y4qSXsNaiPvl/OVbNWW/cu5td1VndItYhH6/gL7Z5W/r4MOeTlz/fOdXfjrRJou2f3UiPQwLV9RdOH3oS4/BUe+sviD8Q3eRfWBWWz3yw8f2YNtD4bMztIMMjqthvwdEEb9S9jbxxD0o71Bsrz/FwPi7HhSDA+Z/p01Hct8m4wx13ZlKRd4YjyC12FBmi9MSgsrFuWzyQHhHTeBDoALpfuiut3rhVxUtFmVTpy6p9vil7C5J5pok4MXPH0dJCyDNQz05ww5+fD+tfksIEpFeokRpN226F+P21oQVFUWwYIaXaFlG/hfvwmnlfQIDAQAB");
|
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAve3MabX/rp3LbE7/zNqKxuid8WT7y4qSXsNaiPvl/OVbNWW/cu5td1VndItYhH6/gL7Z5W/r4MOeTlz/fOdXfjrRJou2f3UiPQwLV9RdOH3oS4/BUe+sviD8Q3eRfWBWWz3yw8f2YNtD4bMztIMMjqthvwdEEb9S9jbxxD0o71Bsrz/FwPi7HhSDA+Z/p01Hct8m4wx13ZlKRd4YjyC12FBmi9MSgsrFuWzyQHhHTeBDoALpfuiut3rhVxUtFmVTpy6p9vil7C5J5pok4MXPH0dJCyDNQz05ww5+fD+tfksIEpFeokRpN226F+P21oQVFUWwYIaXaFlG/hfvwmnlfQIDAQAB");
|
||||||
when(config.isClientIdValidationDisabled()).thenReturn(true);
|
when(config.isClientIdValidationDisabled()).thenReturn(true);
|
||||||
|
|
||||||
final ProviderDetails providerDetails = mock(ProviderDetails.class);
|
|
||||||
when(providerDetails.getIssuerUri()).thenReturn("https://my.issuer");
|
when(providerDetails.getIssuerUri()).thenReturn("https://my.issuer");
|
||||||
|
|
||||||
final JwtDecoderProvider provider = new JwtDecoderProvider(config);
|
final JwtDecoderProvider provider = new JwtDecoderProvider(config);
|
||||||
|
|
||||||
final JwtDecoder decoder = provider.createJwtDecoder(null, providerDetails);
|
final JwtDecoder decoder = provider.createJwtDecoder(null, providerDetails);
|
||||||
|
|
||||||
final Jwt decodedToken = decoder.decode("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjIxNDc0ODM2NDcsImp0aSI6IjEyMzQiLCJpc3MiOiJodHRwczovL215Lmlzc3VlciIsInN1YiI6ImFiYzEyMyIsInR5cCI6IkJlYXJlciIsInByZWZlcnJlZF91c2VybmFtZSI6InBpb3RyZWsifQ.k_KaOrLLh3QsT8mKphkcz2vKpulgxp92UoEDccpHJ1mxE3Pa3gFXPKTj4goUBKXieGPZRMvBDhfWNxMvRYZPiQr2NXJKapkh0bTd0qoaSWz9ICe9Nu3eg7_VA_nwUVPz_35wwmrxgVk0_kpUYQN_VtaO7ZgFE2sJzFjbkVls5aqfAMnEjEgQl837hqZvmlW2ZRWebtxXfQxAjtp0gcTg-xtAHKIINYo_1_uAtt_H9L8KqFaioxrVAEDDIlcKnb-Ks3Y62CrZauaGUJeN_aNj2gdOpdkhvCw79yJyZSGZ7okjGbidCNSAf7Bo2Y6h3dP1Gga7kRmD648ftZESrNvbyg");
|
final Jwt decodedToken = decoder.decode(
|
||||||
|
"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjIxNDc0ODM2NDcsImp0aSI6IjEyMzQiLCJpc3MiOiJodHRwczovL215Lmlzc3VlciIsInN1YiI6ImFiYzEyMyIsInR5cCI6IkJlYXJlciIsInByZWZlcnJlZF91c2VybmFtZSI6InBpb3RyZWsifQ.k_KaOrLLh3QsT8mKphkcz2vKpulgxp92UoEDccpHJ1mxE3Pa3gFXPKTj4goUBKXieGPZRMvBDhfWNxMvRYZPiQr2NXJKapkh0bTd0qoaSWz9ICe9Nu3eg7_VA_nwUVPz_35wwmrxgVk0_kpUYQN_VtaO7ZgFE2sJzFjbkVls5aqfAMnEjEgQl837hqZvmlW2ZRWebtxXfQxAjtp0gcTg-xtAHKIINYo_1_uAtt_H9L8KqFaioxrVAEDDIlcKnb-Ks3Y62CrZauaGUJeN_aNj2gdOpdkhvCw79yJyZSGZ7okjGbidCNSAf7Bo2Y6h3dP1Gga7kRmD648ftZESrNvbyg");
|
||||||
assertThat(decodedToken).isNotNull();
|
assertThat(decodedToken).isNotNull();
|
||||||
|
|
||||||
final Map<String, Object> claims = decodedToken.getClaims();
|
final Map<String, Object> claims = decodedToken.getClaims();
|
||||||
assertThat(claims).isNotNull()
|
assertThat(claims).isNotNull()
|
||||||
.isNotEmpty()
|
.isNotEmpty()
|
||||||
.containsEntry(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME, "piotrek");
|
.containsEntry(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME, "piotrek");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldAcceptAndDecodeAtJwtToken() throws JOSEException
|
||||||
|
{
|
||||||
|
when(config.isClientIdValidationDisabled()).thenReturn(true);
|
||||||
|
when(config.getSignatureAlgorithms()).thenReturn(Set.of(SignatureAlgorithm.PS256, SignatureAlgorithm.ES512));
|
||||||
|
when(restOperations.exchange(any(), any(Class.class))).thenReturn(responseEntity);
|
||||||
|
|
||||||
|
final RSAKey rsaKey = getRsaKey();
|
||||||
|
final RSAKey rsaPublicJWK = rsaKey.toPublicJWK();
|
||||||
|
when(responseEntity.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||||
|
when(responseEntity.getBody()).thenReturn(String.format("{\"keys\": [%s]}", rsaPublicJWK.toJSONString()));
|
||||||
|
|
||||||
|
final SignedJWT signedJWT = getSignedJWT(rsaKey, "at+jwt", "userA", "https://my.issuer");
|
||||||
|
signedJWT.sign(new RSASSASigner(rsaKey));
|
||||||
|
|
||||||
|
when(providerDetails.getIssuerUri()).thenReturn("https://my.issuer");
|
||||||
|
when(providerDetails.getJwkSetUri()).thenReturn("https://my.jwkSetUri");
|
||||||
|
|
||||||
|
final JwtDecoderProvider provider = new JwtDecoderProvider(config);
|
||||||
|
|
||||||
|
final JwtDecoder decoder = provider.createJwtDecoder(restOperations, providerDetails);
|
||||||
|
final Jwt decodedToken = decoder.decode(signedJWT.serialize());
|
||||||
|
assertThat(decodedToken).isNotNull();
|
||||||
|
|
||||||
|
final Map<String, Object> claims = decodedToken.getClaims();
|
||||||
|
assertThat(claims).isNotNull()
|
||||||
|
.isNotEmpty()
|
||||||
|
.containsEntry(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME, "userA");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldFailWithNotMatchingAlgorithm() throws JOSEException
|
||||||
|
{
|
||||||
|
when(config.isClientIdValidationDisabled()).thenReturn(true);
|
||||||
|
when(config.getSignatureAlgorithms()).thenReturn(Set.of(SignatureAlgorithm.RS256));
|
||||||
|
|
||||||
|
when(restOperations.exchange(any(), any(Class.class))).thenReturn(responseEntity);
|
||||||
|
|
||||||
|
final RSAKey rsaKey = getRsaKey();
|
||||||
|
final RSAKey rsaPublicJWK = rsaKey.toPublicJWK();
|
||||||
|
when(responseEntity.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||||
|
when(responseEntity.getBody()).thenReturn(String.format("{\"keys\": [%s]}", rsaPublicJWK.toJSONString()));
|
||||||
|
|
||||||
|
final SignedJWT signedJWT = getSignedJWT(rsaKey, "at+jwt", "userA", "https://my.issuer");
|
||||||
|
signedJWT.sign(new RSASSASigner(rsaKey));
|
||||||
|
|
||||||
|
when(providerDetails.getIssuerUri()).thenReturn("https://my.issuer");
|
||||||
|
when(providerDetails.getJwkSetUri()).thenReturn("https://my.jwkSetUri");
|
||||||
|
|
||||||
|
final JwtDecoderProvider provider = new JwtDecoderProvider(config);
|
||||||
|
|
||||||
|
final JwtDecoder decoder = provider.createJwtDecoder(restOperations, providerDetails);
|
||||||
|
assertThrows(BadJwtException.class, () -> decoder.decode(signedJWT.serialize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldFailWithNotAllowedJOSEHeaderTyp() throws JOSEException
|
||||||
|
{
|
||||||
|
when(config.isClientIdValidationDisabled()).thenReturn(true);
|
||||||
|
when(config.getSignatureAlgorithms()).thenReturn(Set.of(SignatureAlgorithm.PS256));
|
||||||
|
when(restOperations.exchange(any(), any(Class.class))).thenReturn(responseEntity);
|
||||||
|
|
||||||
|
final RSAKey rsaKey = getRsaKey();
|
||||||
|
final RSAKey rsaPublicJWK = rsaKey.toPublicJWK();
|
||||||
|
when(responseEntity.getStatusCode()).thenReturn(HttpStatus.OK);
|
||||||
|
when(responseEntity.getBody()).thenReturn(String.format("{\"keys\": [%s]}", rsaPublicJWK.toJSONString()));
|
||||||
|
|
||||||
|
final SignedJWT signedJWT = getSignedJWT(rsaKey, "not-allowed-type", "userA", "https://my.issuer");
|
||||||
|
signedJWT.sign(new RSASSASigner(rsaKey));
|
||||||
|
|
||||||
|
when(providerDetails.getIssuerUri()).thenReturn("https://my.issuer");
|
||||||
|
when(providerDetails.getJwkSetUri()).thenReturn("https://my.jwkSetUri");
|
||||||
|
|
||||||
|
final JwtDecoderProvider provider = new JwtDecoderProvider(config);
|
||||||
|
|
||||||
|
final JwtDecoder decoder = provider.createJwtDecoder(restOperations, providerDetails);
|
||||||
|
assertThrows(BadJwtException.class, () -> decoder.decode(signedJWT.serialize()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -79,7 +182,8 @@ public class IdentityServiceFacadeFactoryBeanTest
|
|||||||
{
|
{
|
||||||
final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER);
|
final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER);
|
||||||
|
|
||||||
final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer("different-issuer"));
|
final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(
|
||||||
|
tokenWithIssuer("different-issuer"));
|
||||||
assertThat(validationResult).isNotNull();
|
assertThat(validationResult).isNotNull();
|
||||||
assertThat(validationResult.hasErrors()).isTrue();
|
assertThat(validationResult.hasErrors()).isTrue();
|
||||||
assertThat(validationResult.getErrors()).hasSize(1);
|
assertThat(validationResult.getErrors()).hasSize(1);
|
||||||
@@ -164,15 +268,35 @@ public class IdentityServiceFacadeFactoryBeanTest
|
|||||||
final JwtAudienceValidator audienceValidator = new JwtAudienceValidator(EXPECTED_AUDIENCE);
|
final JwtAudienceValidator audienceValidator = new JwtAudienceValidator(EXPECTED_AUDIENCE);
|
||||||
|
|
||||||
final Jwt token = Jwt.withTokenValue(UUID.randomUUID().toString())
|
final Jwt token = Jwt.withTokenValue(UUID.randomUUID().toString())
|
||||||
.claim("aud", EXPECTED_AUDIENCE)
|
.claim("aud", EXPECTED_AUDIENCE)
|
||||||
.header("JUST", "FOR TESTING")
|
.header("JUST", "FOR TESTING")
|
||||||
.build();
|
.build();
|
||||||
final OAuth2TokenValidatorResult validationResult = audienceValidator.validate(token);
|
final OAuth2TokenValidatorResult validationResult = audienceValidator.validate(token);
|
||||||
assertThat(validationResult).isNotNull();
|
assertThat(validationResult).isNotNull();
|
||||||
assertThat(validationResult.hasErrors()).isFalse();
|
assertThat(validationResult.hasErrors()).isFalse();
|
||||||
assertThat(validationResult.getErrors()).isEmpty();
|
assertThat(validationResult.getErrors()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static RSAKey getRsaKey() throws JOSEException
|
||||||
|
{
|
||||||
|
return new RSAKeyGenerator(2048)
|
||||||
|
.keyUse(KeyUse.SIGNATURE)
|
||||||
|
.algorithm(new Algorithm("PS256"))
|
||||||
|
.keyID(KEY_ID)
|
||||||
|
.generate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SignedJWT getSignedJWT(RSAKey rsaKey, String type, String usernameClaim, String issuer)
|
||||||
|
{
|
||||||
|
final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
|
||||||
|
.issuer(issuer)
|
||||||
|
.claim(PersonClaims.PREFERRED_USERNAME_CLAIM_NAME, usernameClaim)
|
||||||
|
.build();
|
||||||
|
return new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.PS256)
|
||||||
|
.type(new JOSEObjectType(type))
|
||||||
|
.keyID(rsaKey.getKeyID()).build(), claimsSet);
|
||||||
|
}
|
||||||
|
|
||||||
private Jwt tokenWithIssuer(String issuer)
|
private Jwt tokenWithIssuer(String issuer)
|
||||||
{
|
{
|
||||||
return Jwt.withTokenValue(UUID.randomUUID().toString())
|
return Jwt.withTokenValue(UUID.randomUUID().toString())
|
||||||
|
Reference in New Issue
Block a user