diff --git a/scm-webapp/src/main/java/sonia/scm/importexport/RepositoryImportLoggerFactory.java b/scm-webapp/src/main/java/sonia/scm/importexport/RepositoryImportLoggerFactory.java index 4cf01198ac..600ac0e472 100644 --- a/scm-webapp/src/main/java/sonia/scm/importexport/RepositoryImportLoggerFactory.java +++ b/scm-webapp/src/main/java/sonia/scm/importexport/RepositoryImportLoggerFactory.java @@ -24,7 +24,11 @@ package sonia.scm.importexport; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.subject.Subject; import sonia.scm.NotFoundException; +import sonia.scm.store.DataStore; import sonia.scm.store.DataStoreFactory; import javax.inject.Inject; @@ -45,7 +49,9 @@ public class RepositoryImportLoggerFactory { } public void getLog(String logId, OutputStream out) { - RepositoryImportLog log = dataStoreFactory.withType(RepositoryImportLog.class).withName("imports").build().getOptional(logId).orElseThrow(() -> new NotFoundException("Log", logId)); + DataStore importStore = dataStoreFactory.withType(RepositoryImportLog.class).withName("imports").build(); + RepositoryImportLog log = importStore.getOptional(logId).orElseThrow(() -> new NotFoundException("Log", logId)); + checkPermission(log); PrintStream printStream = new PrintStream(out); log.toLogHeader().forEach(printStream::println); log.getEntries() @@ -53,4 +59,11 @@ public class RepositoryImportLoggerFactory { .map(RepositoryImportLog.Entry::toLogMessage) .forEach(printStream::println); } + + private void checkPermission(RepositoryImportLog log) { + Subject subject = SecurityUtils.getSubject(); + if (!subject.isPermitted("only:admin:allowed") && !subject.getPrincipal().toString().equals(log.getUserId())) { + throw new AuthorizationException("not permitted"); + } + } } diff --git a/scm-webapp/src/test/java/sonia/scm/importexport/RepositoryImportLoggerFactoryTest.java b/scm-webapp/src/test/java/sonia/scm/importexport/RepositoryImportLoggerFactoryTest.java index cf67790ce0..071a7c51c0 100644 --- a/scm-webapp/src/test/java/sonia/scm/importexport/RepositoryImportLoggerFactoryTest.java +++ b/scm-webapp/src/test/java/sonia/scm/importexport/RepositoryImportLoggerFactoryTest.java @@ -24,6 +24,11 @@ package sonia.scm.importexport; +import org.apache.shiro.authz.AuthorizationException; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.util.ThreadContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import sonia.scm.NotFoundException; import sonia.scm.store.InMemoryDataStore; @@ -33,26 +38,55 @@ import java.io.ByteArrayOutputStream; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class RepositoryImportLoggerFactoryTest { + private final Subject subject = mock(Subject.class); + private final InMemoryDataStore store = new InMemoryDataStore<>(); private final RepositoryImportLoggerFactory factory = new RepositoryImportLoggerFactory(new InMemoryDataStoreFactory(store)); + @BeforeEach + void initSubject() { + ThreadContext.bind(subject); + } + + @AfterEach + void cleanupSubject() { + ThreadContext.unbindSubject(); + } + @Test - void shouldReadLog() { - RepositoryImportLog log = new RepositoryImportLog(); - log.setRepositoryType("git"); - log.setNamespace("hitchhiker"); - log.setName("HeartOfGold"); - log.setUserId("dent"); - log.setUserName("Arthur Dent"); - log.setSuccess(true); + void shouldReadLogForExportingUser() { + when(subject.getPrincipal()).thenReturn("dent"); - log.addEntry(new RepositoryImportLog.Entry("import started")); - log.addEntry(new RepositoryImportLog.Entry("import finished")); + createLog(); - store.put("42", log); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + factory.getLog("42", out); + + assertThat(out).asString().contains( + "Import of repository hitchhiker/HeartOfGold", + "Repository type: null", + "Imported from: null", + "Imported by dent (Arthur Dent)", + "Finished successful" + ) + .containsPattern(".+ - import started") + .containsPattern(".+ - import finished"); + } + + @Test + void shouldReadLogForAdmin() { + when(subject.getPrincipal()).thenReturn("trillian"); + when(subject.isPermitted(anyString())).thenReturn(true); + + createLog(); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -75,4 +109,31 @@ class RepositoryImportLoggerFactoryTest { assertThrows(NotFoundException.class, () -> factory.getLog("42", out)); } + + @Test + void shouldFailWithoutPermission() { + when(subject.getPrincipal()).thenReturn("trillian"); + createLog(); + + doThrow(AuthorizationException.class).when(subject).checkPermission("only:admin:allowed"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + assertThrows(AuthorizationException.class, () -> factory.getLog("42", out)); + } + + private void createLog() { + RepositoryImportLog log = new RepositoryImportLog(); + log.setRepositoryType("git"); + log.setNamespace("hitchhiker"); + log.setName("HeartOfGold"); + log.setUserId("dent"); + log.setUserName("Arthur Dent"); + log.setSuccess(true); + + log.addEntry(new RepositoryImportLog.Entry("import started")); + log.addEntry(new RepositoryImportLog.Entry("import finished")); + + store.put("42", log); + } }