mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 16:35:45 +01:00
implement repository public flag migration to repositoryPermissions for _anonymous user
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
package sonia.scm.update.repository;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.SCMContext;
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
import sonia.scm.migration.UpdateStep;
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.xml.XmlUserDAO;
|
||||||
|
import sonia.scm.version.Version;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
|
||||||
|
import static sonia.scm.version.Version.parse;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class PublicFlagUpdateStep implements UpdateStep {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(PublicFlagUpdateStep.class);
|
||||||
|
|
||||||
|
private static final String V1_REPOSITORY_BACKUP_FILENAME = "repositories.xml.v1.backup";
|
||||||
|
|
||||||
|
private final SCMContextProvider contextProvider;
|
||||||
|
private final XmlUserDAO userDAO;
|
||||||
|
private final XmlRepositoryDAO repositoryDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PublicFlagUpdateStep(SCMContextProvider contextProvider, XmlUserDAO userDAO, XmlRepositoryDAO repositoryDAO) {
|
||||||
|
this.contextProvider = contextProvider;
|
||||||
|
this.userDAO = userDAO;
|
||||||
|
this.repositoryDAO = repositoryDAO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doUpdate() throws JAXBException {
|
||||||
|
createNewAnonymousUserIfNotExists();
|
||||||
|
deleteOldAnonymousUserIfAvailable();
|
||||||
|
|
||||||
|
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class);
|
||||||
|
LOG.info("Migrating public flags of repositories as RepositoryRolePermission 'READ' for user '_anonymous'");
|
||||||
|
V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_BACKUP_FILENAME).ifPresent(
|
||||||
|
this::addRepositoryReadPermissionForAnonymousUser
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Version getTargetVersion() {
|
||||||
|
return parse("2.0.3");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAffectedDataType() {
|
||||||
|
return "sonia.scm.repository.xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRepositoryReadPermissionForAnonymousUser(V1RepositoryHelper.V1RepositoryDatabase v1RepositoryDatabase) {
|
||||||
|
User v2AnonymousUser = userDAO.get(SCMContext.USER_ANONYMOUS);
|
||||||
|
v1RepositoryDatabase.repositoryList.repositories
|
||||||
|
.stream()
|
||||||
|
.filter(V1Repository::isPublic)
|
||||||
|
.forEach(v1Repository -> {
|
||||||
|
Repository v2Repository = repositoryDAO.get(v1Repository.getId());
|
||||||
|
LOG.info(String.format("Add RepositoryRole 'READ' to _anonymous user for repository: %s - %s/%s", v2Repository.getId(), v2Repository.getNamespace(), v2Repository.getName()));
|
||||||
|
v2Repository.addPermission(new RepositoryPermission(v2AnonymousUser.getId(), "READ", false));
|
||||||
|
repositoryDAO.modify(v2Repository);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNewAnonymousUserIfNotExists() {
|
||||||
|
if (!userExists(SCMContext.USER_ANONYMOUS)) {
|
||||||
|
LOG.info("Create new _anonymous user");
|
||||||
|
userDAO.add(SCMContext.ANONYMOUS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteOldAnonymousUserIfAvailable() {
|
||||||
|
String oldAnonymous = "anonymous";
|
||||||
|
if (userExists(oldAnonymous)) {
|
||||||
|
User anonymousUser = userDAO.get(oldAnonymous);
|
||||||
|
LOG.info("Delete obsolete anonymous user");
|
||||||
|
userDAO.delete(anonymousUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean userExists(String username) {
|
||||||
|
return userDAO
|
||||||
|
.getAll()
|
||||||
|
.stream()
|
||||||
|
.anyMatch(user -> user.getName().equals(username));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import sonia.scm.update.V1Properties;
|
|||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ public class V1Repository {
|
|||||||
private String description;
|
private String description;
|
||||||
private String id;
|
private String id;
|
||||||
private String name;
|
private String name;
|
||||||
|
@XmlElement(name="public")
|
||||||
private boolean isPublic;
|
private boolean isPublic;
|
||||||
private boolean archived;
|
private boolean archived;
|
||||||
private String type;
|
private String type;
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package sonia.scm.update.repository;
|
||||||
|
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
import sonia.scm.store.StoreConstants;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.util.Optional.empty;
|
||||||
|
import static java.util.Optional.of;
|
||||||
|
|
||||||
|
class V1RepositoryHelper {
|
||||||
|
|
||||||
|
static File resolveV1File(SCMContextProvider contextProvider, String filename) {
|
||||||
|
return contextProvider
|
||||||
|
.resolve(
|
||||||
|
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(filename)
|
||||||
|
).toFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<V1RepositoryDatabase> readV1Database(JAXBContext jaxbContext, SCMContextProvider contextProvider, String filename) throws JAXBException {
|
||||||
|
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File(contextProvider,filename));
|
||||||
|
if (unmarshal instanceof V1RepositoryHelper.V1RepositoryDatabase) {
|
||||||
|
return of((V1RepositoryHelper.V1RepositoryDatabase) unmarshal);
|
||||||
|
} else {
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class RepositoryList {
|
||||||
|
@XmlElement(name = "repository")
|
||||||
|
List<V1Repository> repositories;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlRootElement(name = "repository-db")
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
static class V1RepositoryDatabase {
|
||||||
|
long creationTime;
|
||||||
|
Long lastModified;
|
||||||
|
@XmlElement(name = "repositories")
|
||||||
|
RepositoryList repositoryList;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,24 +19,17 @@ import sonia.scm.version.Version;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.xml.bind.JAXBContext;
|
import javax.xml.bind.JAXBContext;
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Optional.empty;
|
|
||||||
import static java.util.Optional.of;
|
|
||||||
import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER;
|
import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER;
|
||||||
|
import static sonia.scm.update.repository.V1RepositoryHelper.resolveV1File;
|
||||||
import static sonia.scm.version.Version.parse;
|
import static sonia.scm.version.Version.parse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,6 +52,8 @@ import static sonia.scm.version.Version.parse;
|
|||||||
@Extension
|
@Extension
|
||||||
public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||||
|
|
||||||
|
private final String V1_REPOSITORY_FILENAME = "repositories" + StoreConstants.FILE_EXTENSION;
|
||||||
|
|
||||||
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
||||||
|
|
||||||
private final SCMContextProvider contextProvider;
|
private final SCMContextProvider contextProvider;
|
||||||
@@ -97,12 +92,12 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doUpdate() throws JAXBException {
|
public void doUpdate() throws JAXBException {
|
||||||
if (!resolveV1File().exists()) {
|
if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).exists()) {
|
||||||
LOG.info("no v1 repositories database file found");
|
LOG.info("no v1 repositories database file found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
|
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class);
|
||||||
readV1Database(jaxbContext).ifPresent(
|
V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_FILENAME).ifPresent(
|
||||||
v1Database -> {
|
v1Database -> {
|
||||||
v1Database.repositoryList.repositories.forEach(this::readMigrationEntry);
|
v1Database.repositoryList.repositories.forEach(this::readMigrationEntry);
|
||||||
v1Database.repositoryList.repositories.forEach(this::update);
|
v1Database.repositoryList.repositories.forEach(this::update);
|
||||||
@@ -112,13 +107,13 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<V1Repository> getRepositoriesWithoutMigrationStrategies() {
|
public List<V1Repository> getRepositoriesWithoutMigrationStrategies() {
|
||||||
if (!resolveV1File().exists()) {
|
if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).exists()) {
|
||||||
LOG.info("no v1 repositories database file found");
|
LOG.info("no v1 repositories database file found");
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlRepositoryV1UpdateStep.V1RepositoryDatabase.class);
|
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryHelper.V1RepositoryDatabase.class);
|
||||||
return readV1Database(jaxbContext)
|
return V1RepositoryHelper.readV1Database(jaxbContext, contextProvider, V1_REPOSITORY_FILENAME)
|
||||||
.map(v1Database -> v1Database.repositoryList.repositories.stream())
|
.map(v1Database -> v1Database.repositoryList.repositories.stream())
|
||||||
.orElse(Stream.empty())
|
.orElse(Stream.empty())
|
||||||
.filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent())
|
.filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent())
|
||||||
@@ -196,33 +191,4 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
|||||||
return new RepositoryPermission(v1Permission.getName(), v1Permission.getType(), v1Permission.isGroupPermission());
|
return new RepositoryPermission(v1Permission.getName(), v1Permission.getType(), v1Permission.isGroupPermission());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<V1RepositoryDatabase> readV1Database(JAXBContext jaxbContext) throws JAXBException {
|
|
||||||
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File());
|
|
||||||
if (unmarshal instanceof V1RepositoryDatabase) {
|
|
||||||
return of((V1RepositoryDatabase) unmarshal);
|
|
||||||
} else {
|
|
||||||
return empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File resolveV1File() {
|
|
||||||
return contextProvider
|
|
||||||
.resolve(
|
|
||||||
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve("repositories" + StoreConstants.FILE_EXTENSION)
|
|
||||||
).toFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RepositoryList {
|
|
||||||
@XmlElement(name = "repository")
|
|
||||||
private List<V1Repository> repositories;
|
|
||||||
}
|
|
||||||
|
|
||||||
@XmlRootElement(name = "repository-db")
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
private static class V1RepositoryDatabase {
|
|
||||||
private long creationTime;
|
|
||||||
private Long lastModified;
|
|
||||||
@XmlElement(name = "repositories")
|
|
||||||
private RepositoryList repositoryList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package sonia.scm.update.repository;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junitpioneer.jupiter.TempDirectory;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.SCMContext;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
import sonia.scm.repository.RepositoryRolePermissions;
|
||||||
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
|
import sonia.scm.update.UpdateStepTestUtil;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.xml.XmlUserDAO;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junitpioneer.jupiter.TempDirectory.TempDir;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@ExtendWith(TempDirectory.class)
|
||||||
|
class PublicFlagUpdateStepTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
XmlUserDAO userDAO;
|
||||||
|
@Mock
|
||||||
|
XmlRepositoryDAO repositoryDAO;
|
||||||
|
@Captor
|
||||||
|
ArgumentCaptor<Repository> repositoryCaptor;
|
||||||
|
|
||||||
|
private UpdateStepTestUtil testUtil;
|
||||||
|
private PublicFlagUpdateStep updateStep;
|
||||||
|
private Repository REPOSITORY = RepositoryTestData.createHeartOfGold();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void mockScmHome(@TempDir Path tempDir) throws IOException {
|
||||||
|
testUtil = new UpdateStepTestUtil(tempDir);
|
||||||
|
updateStep = new PublicFlagUpdateStep(testUtil.getContextProvider(), userDAO, repositoryDAO);
|
||||||
|
|
||||||
|
//prepare backup xml
|
||||||
|
V1RepositoryFileSystem.createV1Home(tempDir);
|
||||||
|
Files.move(tempDir.resolve("config").resolve("repositories.xml"), tempDir.resolve("config").resolve("repositories.xml.v1.backup"));
|
||||||
|
when(repositoryDAO.get((String) any())).thenReturn(REPOSITORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDeleteOldAnonymousUserIfExists() throws JAXBException {
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.singleton(new User("anonymous")));
|
||||||
|
User anonymous = new User("anonymous");
|
||||||
|
doReturn(anonymous).when(userDAO).get("anonymous");
|
||||||
|
doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(userDAO).delete(anonymous);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotTryToDeleteOldAnonymousUserIfNotExists() throws JAXBException {
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.emptyList());
|
||||||
|
doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(userDAO, never()).delete(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateNewAnonymousUserIfNotExists() throws JAXBException {
|
||||||
|
doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.singleton(new User("trillian")));
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(userDAO).add(SCMContext.ANONYMOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotCreateNewAnonymousUserIfAlreadyExists() throws JAXBException {
|
||||||
|
doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.singleton(new User("_anonymous")));
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(userDAO, never()).add(SCMContext.ANONYMOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMigratePublicFlagToAnonymousRepositoryPermission() throws JAXBException {
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.emptyList());
|
||||||
|
when(userDAO.get("_anonymous")).thenReturn(SCMContext.ANONYMOUS);
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(repositoryDAO, times(2)).modify(repositoryCaptor.capture());
|
||||||
|
|
||||||
|
RepositoryPermission migratedRepositoryPermission = repositoryCaptor.getValue().getPermissions().iterator().next();
|
||||||
|
assertThat(migratedRepositoryPermission.getName()).isEqualTo(SCMContext.USER_ANONYMOUS);
|
||||||
|
assertThat(migratedRepositoryPermission.getRole()).isEqualTo("READ");
|
||||||
|
assertThat(migratedRepositoryPermission.isGroupPermission()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user