From 4929784a2bea329bbd441b501e72c7239066f3ed Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Wed, 5 Aug 2020 14:11:41 +0200 Subject: [PATCH] create update step for migration of anonymous mode --- .../repository/AnonymousModeUpdateStep.java | 100 +++++++++++++++++ .../resources/AuthenticationResourceTest.java | 20 ++-- .../AnonymousModeUpdateStepTest.java | 102 ++++++++++++++++++ .../scm/update/security/config-withAnon.xml | 44 ++++++++ 4 files changed, 256 insertions(+), 10 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/update/repository/AnonymousModeUpdateStep.java create mode 100644 scm-webapp/src/test/java/sonia/scm/update/repository/AnonymousModeUpdateStepTest.java create mode 100644 scm-webapp/src/test/resources/sonia/scm/update/security/config-withAnon.xml diff --git a/scm-webapp/src/main/java/sonia/scm/update/repository/AnonymousModeUpdateStep.java b/scm-webapp/src/main/java/sonia/scm/update/repository/AnonymousModeUpdateStep.java new file mode 100644 index 0000000000..1fc91c5b53 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/update/repository/AnonymousModeUpdateStep.java @@ -0,0 +1,100 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.update.repository; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import sonia.scm.SCMContextProvider; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.migration.UpdateStep; +import sonia.scm.security.AnonymousMode; +import sonia.scm.store.ConfigurationStore; +import sonia.scm.store.ConfigurationStoreFactory; +import sonia.scm.store.StoreConstants; +import sonia.scm.version.Version; + +import javax.inject.Inject; +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.XmlRootElement; +import java.io.File; +import java.nio.file.Path; + +import static sonia.scm.version.Version.parse; + +public class AnonymousModeUpdateStep implements UpdateStep { + + private final SCMContextProvider contextProvider; + private final ConfigurationStore configStore; + + @Inject + public AnonymousModeUpdateStep(SCMContextProvider contextProvider, ConfigurationStoreFactory configurationStoreFactory) { + this.contextProvider = contextProvider; + this.configStore = configurationStoreFactory.withType(ScmConfiguration.class).withName("config").build(); + } + + @Override + public void doUpdate() throws JAXBException { + Path configFile = determineConfigDirectory().resolve("config" + StoreConstants.FILE_EXTENSION); + + if (configFile.toFile().exists()) { + ScmConfiguration config = configStore.get(); + if (getPreUpdateScmConfigurationFromOldConfig(configFile).isAnonymousAccessEnabled()) { + config.setAnonymousMode(AnonymousMode.PROTOCOL_ONLY); + } else { + config.setAnonymousMode(AnonymousMode.OFF); + } + } + } + + @Override + public Version getTargetVersion() { + return parse("2.4.0"); + } + + @Override + public String getAffectedDataType() { + return "config.xml"; + } + + private PreUpdateScmConfiguration getPreUpdateScmConfigurationFromOldConfig(Path configFile) throws JAXBException { + JAXBContext jaxbContext = JAXBContext.newInstance(AnonymousModeUpdateStep.PreUpdateScmConfiguration.class); + return (AnonymousModeUpdateStep.PreUpdateScmConfiguration) jaxbContext.createUnmarshaller().unmarshal(configFile.toFile()); + } + + private Path determineConfigDirectory() { + return new File(contextProvider.getBaseDirectory(), StoreConstants.CONFIG_DIRECTORY_NAME).toPath(); + } + + @XmlRootElement(name = "scm-config") + @XmlAccessorType(XmlAccessType.FIELD) + @NoArgsConstructor + @Getter + static class PreUpdateScmConfiguration { + private boolean anonymousAccessEnabled; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java index 9991575103..d9524f3ea2 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/AuthenticationResourceTest.java @@ -67,7 +67,7 @@ public class AuthenticationResourceTest { @Rule public ShiroRule shiro = new ShiroRule(); - private RestDispatcher dispatcher = new RestDispatcher(); + private final RestDispatcher dispatcher = new RestDispatcher(); @Mock private AccessTokenBuilderFactory accessTokenBuilderFactory; @@ -75,43 +75,43 @@ public class AuthenticationResourceTest { @Mock private AccessTokenBuilder accessTokenBuilder; - private AccessTokenCookieIssuer cookieIssuer = new DefaultAccessTokenCookieIssuer(mock(ScmConfiguration.class)); + private final AccessTokenCookieIssuer cookieIssuer = new DefaultAccessTokenCookieIssuer(mock(ScmConfiguration.class)); - private MockHttpResponse response = new MockHttpResponse(); + private final MockHttpResponse response = new MockHttpResponse(); private static final String AUTH_JSON_TRILLIAN = "{\n" + "\t\"cookie\": true,\n" + - "\t\"grant_type\": \"password\",\n" + + "\t\"grantType\": \"password\",\n" + "\t\"username\": \"trillian\",\n" + "\t\"password\": \"secret\"\n" + "}"; - private static final String AUTH_FORMENCODED_TRILLIAN = "cookie=true&grant_type=password&username=trillian&password=secret"; + private static final String AUTH_FORMENCODED_TRILLIAN = "cookie=true&grantType=password&username=trillian&password=secret"; private static final String AUTH_JSON_TRILLIAN_WRONG_PW = "{\n" + "\t\"cookie\": true,\n" + - "\t\"grant_type\": \"password\",\n" + + "\t\"grantType\": \"password\",\n" + "\t\"username\": \"trillian\",\n" + "\t\"password\": \"justWrong\"\n" + "}"; private static final String AUTH_JSON_NOT_EXISTING_USER = "{\n" + "\t\"cookie\": true,\n" + - "\t\"grant_type\": \"password\",\n" + + "\t\"grantType\": \"password\",\n" + "\t\"username\": \"iDoNotExist\",\n" + "\t\"password\": \"doesNotMatter\"\n" + "}"; private static final String AUTH_JSON_WITHOUT_USERNAME = String.join("\n", "{", - "\"grant_type\": \"password\",", + "\"grantType\": \"password\",", "\"password\": \"tricia123\"", "}" ); private static final String AUTH_JSON_WITHOUT_PASSWORD = String.join("\n", "{", - "\"grant_type\": \"password\",", + "\"grantType\": \"password\",", "\"username\": \"trillian\"", "}" ); @@ -125,7 +125,7 @@ public class AuthenticationResourceTest { private static final String AUTH_JSON_WITH_INVALID_GRANT_TYPE = String.join("\n", "{", - "\"grant_type\": \"el speciale\",", + "\"grantType\": \"el speciale\",", "\"username\": \"trillian\",", "\"password\": \"tricia123\"", "}" diff --git a/scm-webapp/src/test/java/sonia/scm/update/repository/AnonymousModeUpdateStepTest.java b/scm-webapp/src/test/java/sonia/scm/update/repository/AnonymousModeUpdateStepTest.java new file mode 100644 index 0000000000..15eb828206 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/update/repository/AnonymousModeUpdateStepTest.java @@ -0,0 +1,102 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.update.repository; + +import com.google.common.io.Resources; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.SCMContextProvider; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.AnonymousMode; +import sonia.scm.store.ConfigurationStore; +import sonia.scm.store.InMemoryConfigurationStoreFactory; + +import javax.xml.bind.JAXBException; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; +import static sonia.scm.store.InMemoryConfigurationStoreFactory.create; + +@ExtendWith(MockitoExtension.class) +class AnonymousModeUpdateStepTest { + + @Mock + private SCMContextProvider contextProvider; + + private AnonymousModeUpdateStep updateStep; + private ConfigurationStore configurationStore; + + private Path configDir; + + @BeforeEach + void initUpdateStep(@TempDir Path tempDir) throws IOException { + when(contextProvider.getBaseDirectory()).thenReturn(tempDir.toFile()); + configDir = tempDir.resolve("config"); + Files.createDirectories(configDir); + InMemoryConfigurationStoreFactory inMemoryConfigurationStoreFactory = create(); + configurationStore = inMemoryConfigurationStoreFactory.get("config", null); + updateStep = new AnonymousModeUpdateStep(contextProvider, inMemoryConfigurationStoreFactory); + } + + @Test + void shouldNotUpdateIfConfigFileNotAvailable() throws JAXBException { + updateStep.doUpdate(); + + assertThat(configurationStore.getOptional()).isNotPresent(); + } + + @Test + void shouldUpdateDisabledAnonymousMode() throws JAXBException, IOException { + copyTestDatabaseFile(configDir, "config.xml", "config.xml"); + configurationStore.set(new ScmConfiguration()); + + updateStep.doUpdate(); + + assertThat((configurationStore.get()).getAnonymousMode()).isEqualTo(AnonymousMode.OFF); + } + + @Test + void shouldUpdateEnabledAnonymousMode() throws JAXBException, IOException { + copyTestDatabaseFile(configDir, "config-withAnon.xml", "config.xml"); + configurationStore.set(new ScmConfiguration()); + + updateStep.doUpdate(); + + assertThat((configurationStore.get()).getAnonymousMode()).isEqualTo(AnonymousMode.PROTOCOL_ONLY); + } + + private void copyTestDatabaseFile(Path configDir, String sourceFileName, String targetFileName) throws IOException { + URL url = Resources.getResource("sonia/scm/update/security/" + sourceFileName); + Files.copy(url.openStream(), configDir.resolve(targetFileName)); + } +} diff --git a/scm-webapp/src/test/resources/sonia/scm/update/security/config-withAnon.xml b/scm-webapp/src/test/resources/sonia/scm/update/security/config-withAnon.xml new file mode 100644 index 0000000000..1ae982be55 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/update/security/config-withAnon.xml @@ -0,0 +1,44 @@ + + + + admins,vogons + arthur,dent,ldap-admin + http://localhost:8081/scm + false + false + 80 + http://plugins.scm-manager.org/scm-plugin-backend/api/{version}/plugins?os={os}&arch={arch}&snapshot=false + 8080 + proxy.mydomain.com + localhost + false + false + 8181 + false + Y-m-d H:i:s + true +