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:
René Pfeuffer
2020-12-16 10:58:29 +01:00
committed by GitHub
parent b167d90fea
commit 8e3b0e4145
77 changed files with 2066 additions and 438 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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"));

View File

@@ -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));

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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));
}
}