mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-05 19:29:14 +02:00
Archive repository (#1477)
This adds a flag "archived" to repositories. Repositories marked with this can no longer be modified in any way. To do this, we switch to a new version of Shiro Static Permissions (sdorra/shiro-static-permissions#4) and specify a permission guard to check for every permission request, whether the repository in question is archived or not. Further we implement checks in stores and other activies so that no writing request may be executed by mistake. Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
@@ -673,6 +673,43 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
||||
verify(ubc).unbundle(any(File.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMarkRepositoryAsArchived() throws Exception {
|
||||
String namespace = "space";
|
||||
String name = "repo";
|
||||
Repository repository = mockRepository(namespace, name);
|
||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/archive")
|
||||
.content(new byte[]{});
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(SC_NO_CONTENT, response.getStatus());
|
||||
verify(repositoryManager).archive(repository);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRemoveArchiveMarkFromRepository() throws Exception {
|
||||
String namespace = "space";
|
||||
String name = "repo";
|
||||
Repository repository = mockRepository(namespace, name);
|
||||
repository.setArchived(true);
|
||||
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository);
|
||||
|
||||
MockHttpRequest request = MockHttpRequest
|
||||
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/unarchive")
|
||||
.content(new byte[]{});
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
|
||||
dispatcher.invoke(request, response);
|
||||
|
||||
assertEquals(SC_NO_CONTENT, response.getStatus());
|
||||
verify(repositoryManager).unarchive(repository);
|
||||
}
|
||||
|
||||
private PageResult<Repository> createSingletonPageResult(Repository repository) {
|
||||
return new PageResult<>(singletonList(repository), 0);
|
||||
}
|
||||
|
||||
@@ -266,6 +266,24 @@ public class RepositoryToRepositoryDtoMapperTest {
|
||||
assertEquals("http://1", dto.getLinks().getLinkBy("id").get().getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateArchiveLink() {
|
||||
RepositoryDto dto = mapper.map(createTestRepository());
|
||||
assertEquals(
|
||||
"http://example.com/base/v2/repositories/testspace/test/archive",
|
||||
dto.getLinks().getLinkBy("archive").get().getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateUnArchiveLink() {
|
||||
Repository repository = createTestRepository();
|
||||
repository.setArchived(true);
|
||||
RepositoryDto dto = mapper.map(repository);
|
||||
assertEquals(
|
||||
"http://example.com/base/v2/repositories/testspace/test/unarchive",
|
||||
dto.getLinks().getLinkBy("unarchive").get().getHref());
|
||||
}
|
||||
|
||||
private ScmProtocol mockProtocol(String type, String protocol) {
|
||||
return new MockScmProtocol(type, protocol);
|
||||
}
|
||||
|
||||
@@ -78,13 +78,16 @@ import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
@@ -101,6 +104,8 @@ import static org.mockito.Mockito.when;
|
||||
)
|
||||
public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
|
||||
private RepositoryDAO repositoryDAO;
|
||||
|
||||
{
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
@@ -457,6 +462,77 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
.contains("default_namespace");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMarkRepositoryAsArchived() {
|
||||
Repository repository = createTestRepository();
|
||||
RepositoryManager repoManager = (RepositoryManager) manager;
|
||||
|
||||
repoManager.archive(repository);
|
||||
|
||||
verify(repositoryDAO).modify(argThat(Repository::isArchived));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "dent")
|
||||
public void shouldNotMarkRepositoryAsArchivedWithoutPermission() {
|
||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
||||
when(repositoryDAO.get(repository.getNamespaceAndName())).thenReturn(repository);
|
||||
when(repositoryDAO.get(repository.getId())).thenReturn(repository);
|
||||
RepositoryManager repoManager = (RepositoryManager) manager;
|
||||
|
||||
assertThrows(UnauthorizedException.class, () -> repoManager.archive(repository));
|
||||
|
||||
verify(repositoryDAO, never()).modify(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotMarkRepositoryAsArchivedTwice() {
|
||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
||||
repository.setArchived(true);
|
||||
createRepository(repository);
|
||||
RepositoryManager repoManager = (RepositoryManager) manager;
|
||||
|
||||
assertThrows(NoChangesMadeException.class, () -> repoManager.archive(repository));
|
||||
|
||||
verify(repositoryDAO, never()).modify(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRemoveArchiveMarkFromRepository() {
|
||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
||||
repository.setArchived(true);
|
||||
createRepository(repository);
|
||||
RepositoryManager repoManager = (RepositoryManager) manager;
|
||||
|
||||
repoManager.unarchive(repository);
|
||||
|
||||
verify(repositoryDAO).modify(argThat(r -> !r.isArchived()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "dent")
|
||||
public void shouldNotRemoveArchiveMarkFromRepositoryWithoutPermission() {
|
||||
Repository repository = RepositoryTestData.createHeartOfGold();
|
||||
when(repositoryDAO.get(repository.getNamespaceAndName())).thenReturn(repository);
|
||||
when(repositoryDAO.get(repository.getId())).thenReturn(repository);
|
||||
repository.setArchived(true);
|
||||
RepositoryManager repoManager = (RepositoryManager) manager;
|
||||
|
||||
assertThrows(UnauthorizedException.class, () -> repoManager.unarchive(repository));
|
||||
|
||||
verify(repositoryDAO, never()).modify(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotRemoveArchiveMarkFromNotArchivedRepository() {
|
||||
Repository repository = createTestRepository();
|
||||
RepositoryManager repoManager = (RepositoryManager) manager;
|
||||
|
||||
assertThrows(NoChangesMadeException.class, () -> repoManager.unarchive(repository));
|
||||
|
||||
verify(repositoryDAO, never()).modify(any());
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
@@ -466,7 +542,7 @@ public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
|
||||
|
||||
private DefaultRepositoryManager createRepositoryManager(KeyGenerator keyGenerator) {
|
||||
Set<RepositoryHandler> handlerSet = new HashSet<>();
|
||||
RepositoryDAO repositoryDAO = createRepositoryDaoMock();
|
||||
repositoryDAO = createRepositoryDaoMock();
|
||||
mock(ConfigurationStoreFactory.class);
|
||||
handlerSet.add(createRepositoryHandler("dummy", "Dummy"));
|
||||
handlerSet.add(createRepositoryHandler("git", "Git"));
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
@@ -68,7 +68,7 @@ public class DefaultSecuritySystemTest extends AbstractTestBase
|
||||
public void createSecuritySystem()
|
||||
{
|
||||
jaxbConfigurationEntryStoreFactory =
|
||||
spy(new JAXBConfigurationEntryStoreFactory(contextProvider , repositoryLocationResolver, new UUIDKeyGenerator() ) {});
|
||||
spy(new JAXBConfigurationEntryStoreFactory(contextProvider , repositoryLocationResolver, new UUIDKeyGenerator(), null) {});
|
||||
pluginLoader = mock(PluginLoader.class);
|
||||
when(pluginLoader.getUberClassLoader()).thenReturn(ClassLoaders.getContextClassLoader(DefaultSecuritySystem.class));
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.security;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -35,6 +35,7 @@ import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -55,7 +56,7 @@ class SystemRepositoryPermissionProviderTest {
|
||||
.filter(field -> field.getName().startsWith("ACTION_"))
|
||||
.filter(field -> !field.getName().equals("ACTION_HEALTHCHECK"))
|
||||
.map(this::getString)
|
||||
.filter(verb -> !"create".equals(verb))
|
||||
.filter(verb -> !asList("create", "archive").contains(verb))
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class DefaultMigrationStrategyDAOTest {
|
||||
@BeforeEach
|
||||
void initStore(@TempDir Path tempDir) {
|
||||
when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile());
|
||||
storeFactory = new JAXBConfigurationStoreFactory(contextProvider, null);
|
||||
storeFactory = new JAXBConfigurationStoreFactory(contextProvider, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.update.repository;
|
||||
|
||||
import sonia.scm.repository.spi.ZippedRepositoryTestBase;
|
||||
@@ -52,7 +52,7 @@ class V1RepositoryFileSystem {
|
||||
* <id>c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f</id>
|
||||
* <name>some/more/directories/than/one</name>
|
||||
* <public>false</public>
|
||||
* <archived>false</archived>
|
||||
* <archived>true</archived>
|
||||
* <type>git</type>
|
||||
* </repository>
|
||||
* <repository>
|
||||
|
||||
@@ -136,7 +136,19 @@ class XmlRepositoryV1UpdateStepTest {
|
||||
.get()
|
||||
.hasFieldOrPropertyWithValue("type", "git")
|
||||
.hasFieldOrPropertyWithValue("contact", "arthur@dent.uk")
|
||||
.hasFieldOrPropertyWithValue("description", "A repository with two folders.");
|
||||
.hasFieldOrPropertyWithValue("description", "A repository with two folders.")
|
||||
.hasFieldOrPropertyWithValue("archived", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMapArchivedAttribute() throws JAXBException {
|
||||
updateStep.doUpdate();
|
||||
|
||||
Optional<Repository> repository = findByNamespace("namespace-c1597b4f-a9f0-49f7-ad1f-37d3aae1c55f");
|
||||
|
||||
assertThat(repository)
|
||||
.get()
|
||||
.hasFieldOrPropertyWithValue("archived", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -127,6 +127,6 @@ public class DefaultUserManagerTest extends UserManagerTestBase {
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
private XmlUserDAO createXmlUserDAO() {
|
||||
return new XmlUserDAO(new JAXBConfigurationStoreFactory(contextProvider, locationResolver));
|
||||
return new XmlUserDAO(new JAXBConfigurationStoreFactory(contextProvider, locationResolver, null));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user