diff --git a/pom.xml b/pom.xml index d5c3e8dba9..af38d5175a 100644 --- a/pom.xml +++ b/pom.xml @@ -92,7 +92,6 @@ - jitpack https://jitpack.io @@ -168,6 +167,20 @@ test + + + com.github.sdorra.shiro-static-permissions + ssp-lib + ${ssp.version} + + + + com.github.sdorra.shiro-static-permissions + ssp-processor + ${ssp.version} + true + + @@ -552,7 +565,7 @@ 9.2.10.v20150310 - 1.0.0-SNAPSHOT + 967c8fd521 1.4.0 diff --git a/scm-core/pom.xml b/scm-core/pom.xml index d80ff3bcaa..f19ad7453e 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -128,17 +128,16 @@ 2.0.0-SNAPSHOT provided - + + - com.github.sdorra + com.github.sdorra.shiro-static-permissions ssp-lib - ${ssp.version} - com.github.sdorra + com.github.sdorra.shiro-static-permissions ssp-processor - ${ssp.version} true diff --git a/scm-core/src/main/java/sonia/scm/config/Configuration.java b/scm-core/src/main/java/sonia/scm/config/Configuration.java new file mode 100644 index 0000000000..e9bf3528d5 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/config/Configuration.java @@ -0,0 +1,28 @@ +package sonia.scm.config; + +import com.github.sdorra.ssp.PermissionObject; +import com.github.sdorra.ssp.StaticPermissions; + +/** + * Base for all kinds of configurations. + * + * Allows for permission like + * + * + * + *
+ * + * And for permission checks like {@code ConfigurationPermissions.read(configurationObject).check();} + */ +@StaticPermissions( + value = "configuration", + permissions = {"read", "write"}, + globalPermissions = {} +) +public interface Configuration extends PermissionObject { +} diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index c7c3517099..6e1db68f91 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -44,6 +44,7 @@ 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 javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.io.File; import java.util.Set; @@ -57,10 +58,11 @@ import java.util.concurrent.TimeUnit; * * @author Sebastian Sdorra */ + @Singleton @XmlRootElement(name = "scm-config") @XmlAccessorType(XmlAccessType.FIELD) -public class ScmConfiguration { +public class ScmConfiguration implements Configuration { /** * Default JavaScript date format @@ -501,4 +503,12 @@ public class ScmConfiguration { public void setDefaultNamespaceStrategy(String defaultNamespaceStrategy) { this.defaultNamespaceStrategy = defaultNamespaceStrategy; } + + @Override + // Only for permission checks, don't serialize to XML + @XmlTransient + public String getId() { + // Don't change this without migrating SCM permission configuration! + return "global"; + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java index a327b50dae..33b6cb8030 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractRepositoryHandler.java @@ -56,7 +56,7 @@ import sonia.scm.store.ConfigurationStoreFactory; * * @param */ -public abstract class AbstractRepositoryHandler +public abstract class AbstractRepositoryHandler implements RepositoryHandler { diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index aa28403116..536b7bd9e9 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -55,7 +55,7 @@ import java.net.URL; * @param * @author Sebastian Sdorra */ -public abstract class AbstractSimpleRepositoryHandler +public abstract class AbstractSimpleRepositoryHandler extends AbstractRepositoryHandler implements RepositoryDirectoryHandler { public static final String DEFAULT_VERSION_INFORMATION = "unknown"; diff --git a/scm-core/src/main/java/sonia/scm/repository/SimpleRepositoryConfig.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryConfig.java similarity index 82% rename from scm-core/src/main/java/sonia/scm/repository/SimpleRepositoryConfig.java rename to scm-core/src/main/java/sonia/scm/repository/RepositoryConfig.java index 3654b9c8b4..3db13e5618 100644 --- a/scm-core/src/main/java/sonia/scm/repository/SimpleRepositoryConfig.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryConfig.java @@ -33,15 +33,12 @@ package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- - import sonia.scm.Validateable; - -//~--- JDK imports ------------------------------------------------------------ - -import java.io.File; +import sonia.scm.config.Configuration; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import java.io.File; /** * Basic {@link Repository} configuration class. @@ -49,7 +46,7 @@ import javax.xml.bind.annotation.XmlRootElement; * @author Sebastian Sdorra */ @XmlRootElement -public class SimpleRepositoryConfig implements Validateable +public abstract class RepositoryConfig implements Validateable, Configuration { /** @@ -119,4 +116,19 @@ public class SimpleRepositoryConfig implements Validateable /** directory for repositories */ private File repositoryDirectory; + + /** + * Specifies the identifier of the concrete {@link RepositoryConfig} when checking permissions of an object. + * The permission Strings will have the following format: "configuration:*:ID", where the ID part is defined by this + * method. + * + * For example: "configuration:read:git". + * + * No need to serialize this. + * + * @return identifier of this RepositoryConfig in permission strings + */ + @Override + @XmlTransient // Only for permission checks, don't serialize to XML + public abstract String getId(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandlerConfigChangedEvent.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandlerConfigChangedEvent.java index 844c4a78d6..645274e494 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryHandlerConfigChangedEvent.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryHandlerConfigChangedEvent.java @@ -37,7 +37,7 @@ import sonia.scm.event.Event; * @since 2.0.0 */ @Event -public class RepositoryHandlerConfigChangedEvent +public class RepositoryHandlerConfigChangedEvent { private final C configuration; diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java index 0be291d4e1..a90eb4cc34 100644 --- a/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryUtil.java @@ -74,7 +74,7 @@ public final class RepositoryUtil { return getRepositoryId(handler.getConfig(), directory); } - public static String getRepositoryId(SimpleRepositoryConfig config, File directory) throws IOException { + public static String getRepositoryId(RepositoryConfig config, File directory) throws IOException { return getRepositoryId(config.getRepositoryDirectory(), directory); } diff --git a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java index 6d20d14043..fa8f2dd5d5 100644 --- a/scm-core/src/main/java/sonia/scm/web/VndMediaType.java +++ b/scm-core/src/main/java/sonia/scm/web/VndMediaType.java @@ -20,6 +20,8 @@ public class VndMediaType { public static final String REPOSITORY_COLLECTION = PREFIX + "repositoryCollection" + SUFFIX; public static final String ME = PREFIX + "me" + SUFFIX; + public static final String CONFIG = PREFIX + "config" + SUFFIX; + private VndMediaType() { } diff --git a/scm-core/src/test/java/sonia/scm/repository/RepositoryUtilTest.java b/scm-core/src/test/java/sonia/scm/repository/RepositoryUtilTest.java index 25fa3eeeb3..82e85568d0 100644 --- a/scm-core/src/test/java/sonia/scm/repository/RepositoryUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/RepositoryUtilTest.java @@ -21,9 +21,14 @@ public class RepositoryUtilTest { public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Mock - private AbstractRepositoryHandler repositoryHandler; + private AbstractRepositoryHandler repositoryHandler; - private SimpleRepositoryConfig repositoryConfig = new SimpleRepositoryConfig(); + private RepositoryConfig repositoryConfig = new RepositoryConfig() { + @Override + public String getId() { + return "repository"; + } + }; @Before public void setUpMocks() { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java index 80fe8907ac..2ec93ed83d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitConfig.java @@ -39,6 +39,7 @@ 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 javax.xml.bind.annotation.XmlTransient; /** * @@ -46,7 +47,7 @@ import javax.xml.bind.annotation.XmlRootElement; */ @XmlRootElement(name = "config") @XmlAccessorType(XmlAccessType.FIELD) -public class GitConfig extends SimpleRepositoryConfig { +public class GitConfig extends RepositoryConfig { @XmlElement(name = "gc-expression") private String gcExpression; @@ -55,5 +56,11 @@ public class GitConfig extends SimpleRepositoryConfig { { return gcExpression; } - + + @Override + @XmlTransient // Only for permission checks, don't serialize to XML + public String getId() { + // Don't change this without migrating SCM permission configuration! + return "git"; + } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java index b9bd650925..6438f49d4c 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgConfig.java @@ -33,20 +33,19 @@ package sonia.scm.repository; -//~--- non-JDK imports -------------------------------------------------------- import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; + /** * * @author Sebastian Sdorra */ @XmlRootElement(name = "config") -public class HgConfig extends SimpleRepositoryConfig +public class HgConfig extends RepositoryConfig { /** @@ -223,4 +222,11 @@ public class HgConfig extends SimpleRepositoryConfig /** Field description */ private boolean showRevisionInId = false; + + @Override + @XmlTransient // Only for permission checks, don't serialize to XML + public String getId() { + // Don't change this without migrating SCM permission configuration! + return "hg"; + } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java index c4b0af57a1..73b4f39219 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/SvnConfig.java @@ -33,12 +33,11 @@ package sonia.scm.repository; -//~--- JDK imports ------------------------------------------------------------ - 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 javax.xml.bind.annotation.XmlTransient; /** * @@ -46,7 +45,7 @@ import javax.xml.bind.annotation.XmlRootElement; */ @XmlRootElement(name = "config") @XmlAccessorType(XmlAccessType.FIELD) -public class SvnConfig extends SimpleRepositoryConfig +public class SvnConfig extends RepositoryConfig { /** @@ -108,4 +107,11 @@ public class SvnConfig extends SimpleRepositoryConfig /** Field description */ private Compatibility compatibility = Compatibility.NONE; + + @Override + @XmlTransient // Only for permission checks, don't serialize to XML + public String getId() { + // Don't change this without migrating SCM permission configuration! + return "svn"; + } } diff --git a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java index 870127b14e..bf68c27b19 100644 --- a/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java +++ b/scm-test/src/main/java/sonia/scm/repository/DummyRepositoryHandler.java @@ -37,6 +37,7 @@ import sonia.scm.Type; import sonia.scm.io.DefaultFileSystem; import sonia.scm.store.ConfigurationStoreFactory; +import javax.xml.bind.annotation.XmlRootElement; import java.io.File; import java.util.HashSet; import java.util.Set; @@ -48,7 +49,7 @@ import java.util.Set; * @author Sebastian Sdorra */ public class DummyRepositoryHandler - extends AbstractSimpleRepositoryHandler { + extends AbstractSimpleRepositoryHandler { public static final String TYPE_DISPLAYNAME = "Dummy"; @@ -79,12 +80,20 @@ public class DummyRepositoryHandler } @Override - protected SimpleRepositoryConfig createInitialConfig() { - return new SimpleRepositoryConfig(); + protected DummyRepositoryConfig createInitialConfig() { + return new DummyRepositoryConfig(); } @Override - protected Class getConfigClass() { - return SimpleRepositoryConfig.class; + protected Class getConfigClass() { + return DummyRepositoryConfig.class; + } + + @XmlRootElement(name = "config") + public static class DummyRepositoryConfig extends RepositoryConfig { + @Override + public String getId() { + return TYPE_NAME; + } } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractManagerResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractManagerResource.java index b8a3dfc45d..27ceb03a36 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractManagerResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractManagerResource.java @@ -48,13 +48,8 @@ import sonia.scm.util.AssertUtil; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; -import javax.ws.rs.core.CacheControl; -import javax.ws.rs.core.EntityTag; -import javax.ws.rs.core.GenericEntity; -import javax.ws.rs.core.Request; -import javax.ws.rs.core.Response; +import javax.ws.rs.core.*; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.UriInfo; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupResource.java index b9701f7e38..42816abd93 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupResource.java @@ -47,22 +47,8 @@ import sonia.scm.group.GroupException; import sonia.scm.group.GroupManager; import sonia.scm.security.Role; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.GenericEntity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Request; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.*; +import javax.ws.rs.core.*; import java.util.Collection; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index a2be057bdf..1b25fc3f79 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -50,15 +50,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.NotSupportedFeatuerException; import sonia.scm.Type; import sonia.scm.api.rest.RestActionUploadResult; -import sonia.scm.repository.AdvancedImportHandler; -import sonia.scm.repository.ImportHandler; -import sonia.scm.repository.ImportResult; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryAlreadyExistsException; -import sonia.scm.repository.RepositoryException; -import sonia.scm.repository.RepositoryHandler; -import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryType; +import sonia.scm.repository.*; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -66,21 +58,8 @@ import sonia.scm.repository.api.UnbundleCommandBuilder; import sonia.scm.security.Role; import sonia.scm.util.IOUtil; -import javax.ws.rs.Consumes; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.FormParam; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.GenericEntity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.*; +import javax.ws.rs.core.*; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java index 459a094914..9b9688332f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java @@ -46,51 +46,16 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.authz.AuthorizationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.repository.BlameResult; -import sonia.scm.repository.Branches; -import sonia.scm.repository.BrowserResult; -import sonia.scm.repository.Changeset; -import sonia.scm.repository.ChangesetPagingResult; -import sonia.scm.repository.HealthChecker; -import sonia.scm.repository.Permission; -import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryException; -import sonia.scm.repository.RepositoryIsNotArchivedException; -import sonia.scm.repository.RepositoryManager; -import sonia.scm.repository.RepositoryNotFoundException; -import sonia.scm.repository.Tags; -import sonia.scm.repository.api.BlameCommandBuilder; -import sonia.scm.repository.api.BrowseCommandBuilder; -import sonia.scm.repository.api.CatCommandBuilder; -import sonia.scm.repository.api.CommandNotSupportedException; -import sonia.scm.repository.api.DiffCommandBuilder; -import sonia.scm.repository.api.DiffFormat; -import sonia.scm.repository.api.LogCommandBuilder; -import sonia.scm.repository.api.RepositoryService; -import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.repository.*; +import sonia.scm.repository.api.*; import sonia.scm.util.AssertUtil; import sonia.scm.util.HttpUtil; import sonia.scm.util.IOUtil; import sonia.scm.util.Util; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.GenericEntity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Request; -import javax.ws.rs.core.Response; +import javax.ws.rs.*; +import javax.ws.rs.core.*; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.StreamingOutput; -import javax.ws.rs.core.UriInfo; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserResource.java index f9dc40f0fc..8328230672 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserResource.java @@ -50,21 +50,8 @@ import sonia.scm.user.UserManager; import sonia.scm.util.AssertUtil; import sonia.scm.util.Util; -import javax.ws.rs.DELETE; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.GenericEntity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Request; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.*; +import javax.ws.rs.core.*; import java.util.Collection; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java new file mode 100644 index 0000000000..13d9ff5351 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDto.java @@ -0,0 +1,42 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.HalRepresentation; +import de.otto.edison.hal.Links; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.Set; + +@NoArgsConstructor +@Getter +@Setter +public class ConfigDto extends HalRepresentation { + + private String proxyPassword; + private int proxyPort; + private String proxyServer; + private String proxyUser; + private boolean enableProxy; + private String realmDescription; + private boolean enableRepositoryArchive; + private boolean disableGroupingGrid; + private String dateFormat; + private boolean anonymousAccessEnabled; + private Set adminGroups; + private Set adminUsers; + private String baseUrl; + private boolean forceBaseUrl; + private int loginAttemptLimit; + private Set proxyExcludes; + private boolean skipFailedAuthenticators; + private String pluginUrl; + private long loginAttemptLimitTimeout; + private boolean enabledXsrfProtection; + + @Override + @SuppressWarnings("squid:S1185") // We want to have this method available in this package + protected HalRepresentation add(Links links) { + return super.add(links); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapper.java new file mode 100644 index 0000000000..b26d22232d --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapper.java @@ -0,0 +1,12 @@ +package sonia.scm.api.v2.resources; + +import org.mapstruct.Mapper; +import sonia.scm.config.ScmConfiguration; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class ConfigDtoToScmConfigurationMapper { + + public abstract ScmConfiguration map(ConfigDto dto); +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java new file mode 100644 index 0000000000..bf3da19416 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ConfigResource.java @@ -0,0 +1,87 @@ +package sonia.scm.api.v2.resources; + +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.util.ScmConfigurationUtil; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +@Path(ConfigResource.CONFIG_PATH_V2) +public class ConfigResource { + + static final String CONFIG_PATH_V2 = "v2/config"; + private final ConfigDtoToScmConfigurationMapper dtoToConfigMapper; + private final ScmConfigurationToConfigDtoMapper configToDtoMapper; + private final ScmConfiguration configuration; + + @Inject + public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) { + this.dtoToConfigMapper = dtoToConfigMapper; + this.configToDtoMapper = configToDtoMapper; + this.configuration = configuration; + } + + /** + * Returns the global scm config. + */ + @GET + @Path("") + @Produces(VndMediaType.CONFIG) + @TypeHint(UserDto.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to read the global config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + public Response get() { + + // We do this permission check in Resource and not in ScmConfiguration, because it must be available for reading + // from within the code (plugins, etc.), but not for the whole anonymous world outside. + ConfigurationPermissions.read(configuration).check(); + + return Response.ok(configToDtoMapper.map(configuration)).build(); + } + + /** + * Modifies the global scm config. + * + * @param configDto new global scm configuration as DTO + */ + @PUT + @Path("") + @Consumes(VndMediaType.CONFIG) + @StatusCodes({ + @ResponseCode(code = 201, condition = "update success"), + @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"), + @ResponseCode(code = 403, condition = "not authorized, the current user has no privileges to update the global config"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) + public Response update(ConfigDto configDto, @Context UriInfo uriInfo) { + + // This *could* be moved to ScmConfiguration or ScmConfigurationUtil classes. + // But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later. + ConfigurationPermissions.write(configuration).check(); + + ScmConfiguration config = dtoToConfigMapper.map(configDto); + synchronized (ScmConfiguration.class) { + configuration.load(config); + ScmConfigurationUtil.getInstance().store(configuration); + } + + return Response.noContent().build(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java index d806d3cee7..0f68894a64 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupCollectionResource.java @@ -1,23 +1,13 @@ package sonia.scm.api.v2.resources; -import com.webcohesion.enunciate.metadata.rs.ResponseCode; -import com.webcohesion.enunciate.metadata.rs.ResponseHeader; -import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; -import com.webcohesion.enunciate.metadata.rs.StatusCodes; -import com.webcohesion.enunciate.metadata.rs.TypeHint; +import com.webcohesion.enunciate.metadata.rs.*; import sonia.scm.group.Group; import sonia.scm.group.GroupException; import sonia.scm.group.GroupManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; -import javax.ws.rs.Consumes; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import javax.ws.rs.*; import javax.ws.rs.core.Response; import java.io.IOException; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java index c9f066790b..10818ec953 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/GroupResource.java @@ -9,13 +9,7 @@ import sonia.scm.group.GroupManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; +import javax.ws.rs.*; import javax.ws.rs.core.Response; public class GroupResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java index 8e22755fe0..69430bf43b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MapperModule.java @@ -15,6 +15,9 @@ public class MapperModule extends AbstractModule { bind(GroupToGroupDtoMapper.class).to(Mappers.getMapper(GroupToGroupDtoMapper.class).getClass()); bind(GroupCollectionToDtoMapper.class); + bind(ScmConfigurationToConfigDtoMapper.class).to(Mappers.getMapper(ScmConfigurationToConfigDtoMapper.class).getClass()); + bind(ConfigDtoToScmConfigurationMapper.class).to(Mappers.getMapper(ConfigDtoToScmConfigurationMapper.class).getClass()); + bind(RepositoryToRepositoryDtoMapper.class).to(Mappers.getMapper(RepositoryToRepositoryDtoMapper.class).getClass()); bind(RepositoryDtoToRepositoryMapper.class).to(Mappers.getMapper(RepositoryDtoToRepositoryMapper.class).getClass()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java index 7112e7113b..7a70957b91 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryCollectionResource.java @@ -1,23 +1,13 @@ package sonia.scm.api.v2.resources; -import com.webcohesion.enunciate.metadata.rs.ResponseCode; -import com.webcohesion.enunciate.metadata.rs.ResponseHeader; -import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; -import com.webcohesion.enunciate.metadata.rs.StatusCodes; -import com.webcohesion.enunciate.metadata.rs.TypeHint; +import com.webcohesion.enunciate.metadata.rs.*; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryException; import sonia.scm.repository.RepositoryManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; -import javax.ws.rs.Consumes; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import javax.ws.rs.*; import javax.ws.rs.core.Response; public class RepositoryCollectionResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java index 2c02bb8180..61e953602f 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDtoToRepositoryMapper.java @@ -1,10 +1,6 @@ package sonia.scm.api.v2.resources; -import org.mapstruct.AfterMapping; -import org.mapstruct.Context; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; +import org.mapstruct.*; import sonia.scm.repository.Repository; @Mapper diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java index 5972508c78..34896149c3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryResource.java @@ -10,13 +10,7 @@ import sonia.scm.web.VndMediaType; import javax.inject.Inject; import javax.inject.Provider; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; +import javax.ws.rs.*; import javax.ws.rs.core.Response; import java.util.Optional; import java.util.function.Predicate; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index 8f6f1eaae3..d02747d2af 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -76,7 +76,7 @@ class ResourceLinks { return userLinkBuilder.method("getUserResource").parameters(name).method("delete").parameters().href(); } - String update(String name) { + String update(String name) { return userLinkBuilder.method("getUserResource").parameters(name).method("update").parameters().href(); } } @@ -101,6 +101,26 @@ class ResourceLinks { } } + ConfigLinks config() { + return new ConfigLinks(uriInfoStore.get()); + } + + static class ConfigLinks { + private final LinkBuilder configLinkBuilder; + + ConfigLinks(UriInfo uriInfo) { + configLinkBuilder = new LinkBuilder(uriInfo, ConfigResource.class); + } + + String self() { + return configLinkBuilder.method("get").parameters().href(); + } + + String update() { + return configLinkBuilder.method("update").parameters().href(); + } + } + public RepositoryLinks repository() { return new RepositoryLinks(uriInfoStore.get()); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java new file mode 100644 index 0000000000..59e56feb11 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapper.java @@ -0,0 +1,34 @@ +package sonia.scm.api.v2.resources; + +import de.otto.edison.hal.Links; +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.config.ScmConfiguration; + +import javax.inject.Inject; + +import static de.otto.edison.hal.Link.link; +import static de.otto.edison.hal.Links.linkingTo; + +// Mapstruct does not support parameterized (i.e. non-default) constructors. Thus, we need to use field injection. +@SuppressWarnings("squid:S3306") +@Mapper +public abstract class ScmConfigurationToConfigDtoMapper { + + @Inject + private ResourceLinks resourceLinks; + + public abstract ConfigDto map(ScmConfiguration config); + + @AfterMapping + void appendLinks(ScmConfiguration config, @MappingTarget ConfigDto target) { + Links.Builder linksBuilder = linkingTo().self(resourceLinks.config().self()); + if (ConfigurationPermissions.write(config).isPermitted()) { + linksBuilder.single(link("update", resourceLinks.config().update())); + } + target.add(linksBuilder.build()); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java index 578361424c..ef826980b3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserCollectionResource.java @@ -1,23 +1,13 @@ package sonia.scm.api.v2.resources; -import com.webcohesion.enunciate.metadata.rs.ResponseCode; -import com.webcohesion.enunciate.metadata.rs.ResponseHeader; -import com.webcohesion.enunciate.metadata.rs.ResponseHeaders; -import com.webcohesion.enunciate.metadata.rs.StatusCodes; -import com.webcohesion.enunciate.metadata.rs.TypeHint; +import com.webcohesion.enunciate.metadata.rs.*; import sonia.scm.user.User; import sonia.scm.user.UserException; import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; -import javax.ws.rs.Consumes; -import javax.ws.rs.DefaultValue; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import javax.ws.rs.*; import javax.ws.rs.core.Response; import java.io.IOException; diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java index 2dc070d815..f05c8165cb 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/UserResource.java @@ -9,13 +9,7 @@ import sonia.scm.user.UserManager; import sonia.scm.web.VndMediaType; import javax.inject.Inject; -import javax.ws.rs.Consumes; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; +import javax.ws.rs.*; import javax.ws.rs.core.Response; public class UserResource { diff --git a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java index 633f6aa3b5..a5455c6d21 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java @@ -49,6 +49,7 @@ import sonia.scm.security.Role; * * @author Sebastian Sdorra */ +// TODO before releasing v2, delete this filter (we use Permission objects now) @WebElement( value = Filters.PATTERN_CONFIG, morePatterns = { diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java index 4f39fd3196..f22a5519dc 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java +++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java @@ -52,12 +52,7 @@ import sonia.scm.util.CollectionAppender; import sonia.scm.util.Util; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; +import java.util.*; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java index ad6de93b0a..6998893652 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -42,30 +42,14 @@ import com.google.inject.Singleton; import org.apache.shiro.concurrent.SubjectAwareExecutorService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.ArgumentIsInvalidException; -import sonia.scm.ConfigurationException; -import sonia.scm.HandlerEventType; -import sonia.scm.ManagerDaoAdapter; -import sonia.scm.SCMContextProvider; -import sonia.scm.Type; +import sonia.scm.*; import sonia.scm.config.ScmConfiguration; import sonia.scm.security.KeyGenerator; -import sonia.scm.util.AssertUtil; -import sonia.scm.util.CollectionAppender; -import sonia.scm.util.HttpUtil; -import sonia.scm.util.IOUtil; -import sonia.scm.util.Util; +import sonia.scm.util.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index c59ec40d4f..563e140434 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -55,11 +55,7 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java new file mode 100644 index 0000000000..ad1e71368e --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigDtoToScmConfigurationMapperTest.java @@ -0,0 +1,81 @@ +package sonia.scm.api.v2.resources; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.internal.util.collections.Sets; +import sonia.scm.config.ScmConfiguration; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.MockitoAnnotations.initMocks; + +public class ConfigDtoToScmConfigurationMapperTest { + + @InjectMocks + private ConfigDtoToScmConfigurationMapperImpl mapper; + + private String[] expectedUsers = { "trillian", "arthur" }; + private String[] expectedGroups = { "admin", "plebs" }; + private String[] expectedExcludes = { "ex", "clude" }; + + @Before + public void init() { + initMocks(this); + } + + @Test + public void shouldMapFields() { + ConfigDto dto = createDefaultDto(); + ScmConfiguration config = mapper.map(dto); + + assertEquals("prPw" , config.getProxyPassword()); + assertEquals(42 , config.getProxyPort()); + assertEquals("srvr" , config.getProxyServer()); + assertEquals("user" , config.getProxyUser()); + assertTrue(config.isEnableProxy()); + assertEquals("realm" , config.getRealmDescription()); + assertTrue(config.isEnableRepositoryArchive()); + assertTrue(config.isDisableGroupingGrid()); + assertEquals("yyyy" , config.getDateFormat()); + assertTrue(config.isAnonymousAccessEnabled()); + assertTrue("adminGroups", config.getAdminGroups().containsAll(Arrays.asList(expectedGroups))); + assertTrue("adminUsers", config.getAdminUsers().containsAll(Arrays.asList(expectedUsers))); + assertEquals("baseurl" , config.getBaseUrl()); + assertTrue(config.isForceBaseUrl()); + assertEquals(41 , config.getLoginAttemptLimit()); + assertTrue("proxyExcludes", config.getProxyExcludes().containsAll(Arrays.asList(expectedExcludes))); + assertTrue(config.isSkipFailedAuthenticators()); + assertEquals("https://plug.ins" , config.getPluginUrl()); + assertEquals(40 , config.getLoginAttemptLimitTimeout()); + assertTrue(config.isEnabledXsrfProtection()); + } + + private ConfigDto createDefaultDto() { + ConfigDto configDto = new ConfigDto(); + configDto.setProxyPassword("prPw"); + configDto.setProxyPort(42); + configDto.setProxyServer("srvr"); + configDto.setProxyUser("user"); + configDto.setEnableProxy(true); + configDto.setRealmDescription("realm"); + configDto.setEnableRepositoryArchive(true); + configDto.setDisableGroupingGrid(true); + configDto.setDateFormat("yyyy"); + configDto.setAnonymousAccessEnabled(true); + configDto.setAdminGroups(Sets.newSet(expectedGroups)); + configDto.setAdminUsers(Sets.newSet(expectedUsers)); + configDto.setBaseUrl("baseurl"); + configDto.setForceBaseUrl(true); + configDto.setLoginAttemptLimit(41); + configDto.setProxyExcludes(Sets.newSet(expectedExcludes)); + configDto.setSkipFailedAuthenticators(true); + configDto.setPluginUrl("https://plug.ins"); + configDto.setLoginAttemptLimitTimeout(40); + configDto.setEnabledXsrfProtection(true); + + return configDto; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java new file mode 100644 index 0000000000..5365e9fbde --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ConfigResourceTest.java @@ -0,0 +1,127 @@ +package sonia.scm.api.v2.resources; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import com.google.common.io.Resources; +import org.jboss.resteasy.core.Dispatcher; +import org.jboss.resteasy.mock.MockDispatcherFactory; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.InjectMocks; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.web.VndMediaType; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.MockitoAnnotations.initMocks; + +@SubjectAware( + configuration = "classpath:sonia/scm/configuration/shiro.ini", + password = "secret" +) +public class ConfigResourceTest { + + @Rule + public ShiroRule shiro = new ShiroRule(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Dispatcher dispatcher = MockDispatcherFactory.createDispatcher(); + + private final URI baseUri = URI.create("/"); + @SuppressWarnings("unused") // Is injected + private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); + + @InjectMocks + private ConfigDtoToScmConfigurationMapperImpl dtoToConfigMapper; + @InjectMocks + private ScmConfigurationToConfigDtoMapperImpl configToDtoMapper; + + @Before + public void prepareEnvironment() { + initMocks(this); + + ConfigResource configResource = new ConfigResource(dtoToConfigMapper, configToDtoMapper, createConfiguration()); + + dispatcher.getRegistry().addSingletonResource(configResource); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldGetGlobalConfig() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"heartOfGold\"")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config")); + assertFalse("Update link present", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); + } + + @Test + @SubjectAware(username = "writeOnly") + public void shouldGetConfigOnlyWhenAuthorized() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); + MockHttpResponse response = new MockHttpResponse(); + + thrown.expectMessage("Subject does not have permission [configuration:read:global]"); + + dispatcher.invoke(request, response); + } + + @Test + @SubjectAware(username = "readWrite") + public void shouldUpdateConfig() throws URISyntaxException, IOException { + URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json"); + byte[] configJson = Resources.toByteArray(url); + MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2) + .contentType(VndMediaType.CONFIG) + .content(configJson); + + MockHttpResponse response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_NO_CONTENT, response.getStatus()); + + request = MockHttpRequest.get("/" + ConfigResource.CONFIG_PATH_V2); + response = new MockHttpResponse(); + dispatcher.invoke(request, response); + assertEquals(HttpServletResponse.SC_OK, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"proxyPassword\":\"newPassword\"")); + assertTrue(response.getContentAsString().contains("\"self\":{\"href\":\"/v2/config")); + assertTrue("link not found", response.getContentAsString().contains("\"update\":{\"href\":\"/v2/config")); + } + + @Test + @SubjectAware(username = "readOnly") + public void shouldUpdateConfigOnlyWhenAuthorized() throws URISyntaxException, IOException { + URL url = Resources.getResource("sonia/scm/api/v2/config-test-update.json"); + byte[] configJson = Resources.toByteArray(url); + MockHttpRequest request = MockHttpRequest.put("/" + ConfigResource.CONFIG_PATH_V2) + .contentType(VndMediaType.CONFIG) + .content(configJson); + MockHttpResponse response = new MockHttpResponse(); + + thrown.expectMessage("Subject does not have permission [configuration:write:global]"); + + dispatcher.invoke(request, response); + } + + private static ScmConfiguration createConfiguration() { + ScmConfiguration scmConfiguration = new ScmConfiguration(); + scmConfiguration.setProxyPassword("heartOfGold"); + + return scmConfiguration; + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java index 3302d6ef6e..dff9e7e99b 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/GroupRootResourceTest.java @@ -27,9 +27,7 @@ import java.net.URL; import java.util.Collections; import static java.util.Collections.singletonList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; @@ -111,8 +109,6 @@ public class GroupRootResourceTest { Group group = createDummyGroup(); when(groupManager.get("admin")).thenReturn(group); - Group updatedGroup = createDummyGroup(); - updatedGroup.setDescription("Updated description"); MockHttpRequest request = MockHttpRequest .put("/" + GroupRootResource.GROUPS_PATH_V2 + "admin") @@ -134,9 +130,6 @@ public class GroupRootResourceTest { URL url = Resources.getResource("sonia/scm/api/v2/group-test-update.json"); byte[] groupJson = Resources.toByteArray(url); - Group updatedGroup = createDummyGroup(); - updatedGroup.setDescription("Updated description"); - MockHttpRequest request = MockHttpRequest .put("/" + GroupRootResource.GROUPS_PATH_V2 + "idontexist") .contentType(VndMediaType.GROUP) diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java index 24a11fbf09..a8616fe2ce 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/RepositoryRootResourceTest.java @@ -27,19 +27,14 @@ import java.net.URL; import static java.util.Collections.singletonList; import static java.util.Optional.empty; import static java.util.Optional.of; -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; -import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; -import static javax.servlet.http.HttpServletResponse.SC_OK; +import static javax.servlet.http.HttpServletResponse.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; @SubjectAware( diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java index b35bc3820c..b5fe5c4da4 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksMock.java @@ -24,6 +24,7 @@ public class ResourceLinksMock { when(resourceLinks.changesetCollection()).thenReturn(new ResourceLinks.ChangesetCollectionLinks(uriInfo)); when(resourceLinks.sourceCollection()).thenReturn(new ResourceLinks.SourceCollectionLinks(uriInfo)); when(resourceLinks.permissionCollection()).thenReturn(new ResourceLinks.PermissionCollectionLinks(uriInfo)); + when(resourceLinks.config()).thenReturn(new ResourceLinks.ConfigLinks(uriInfo)); return resourceLinks; } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java index c4bc5c73cd..b850a4b4c6 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ResourceLinksTest.java @@ -132,6 +132,18 @@ public class ResourceLinksTest { assertEquals(BASE_URL + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/sources/", url); } + @Test + public void shouldCreateCorrectConfigSelfUrl() { + String url = resourceLinks.config().self(); + assertEquals(BASE_URL + ConfigResource.CONFIG_PATH_V2, url); + } + + @Test + public void shouldCreateCorrectConfigUpdateUrl() { + String url = resourceLinks.config().update(); + assertEquals(BASE_URL + ConfigResource.CONFIG_PATH_V2, url); + } + @Before public void initUriInfo() { initMocks(this); diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java new file mode 100644 index 0000000000..ff20516e5a --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/ScmConfigurationToConfigDtoMapperTest.java @@ -0,0 +1,126 @@ +package sonia.scm.api.v2.resources; + +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.ThreadContext; +import org.apache.shiro.util.ThreadState; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.internal.util.collections.Sets; +import sonia.scm.config.ScmConfiguration; + +import java.net.URI; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +public class ScmConfigurationToConfigDtoMapperTest { + + private URI baseUri = URI.create("http://example.com/base/"); + + private String[] expectedUsers = { "trillian", "arthur" }; + private String[] expectedGroups = { "admin", "plebs" }; + private String[] expectedExcludes = { "ex", "clude" }; + + @SuppressWarnings("unused") // Is injected + private ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); + + @InjectMocks + private ScmConfigurationToConfigDtoMapperImpl mapper; + + private final Subject subject = mock(Subject.class); + private final ThreadState subjectThreadState = new SubjectThreadState(subject); + + private URI expectedBaseUri; + + @Before + public void init() { + initMocks(this); + expectedBaseUri = baseUri.resolve(ConfigResource.CONFIG_PATH_V2); + subjectThreadState.bind(); + ThreadContext.bind(subject); + } + + @After + public void unbindSubject() { + ThreadContext.unbindSubject(); + } + + @Test + public void shouldMapFields() { + ScmConfiguration config = createConfiguration(); + + + when(subject.isPermitted("configuration:write:global")).thenReturn(true); + ConfigDto dto = mapper.map(config); + + assertEquals("heartOfGold" , dto.getProxyPassword()); + assertEquals(1234 , dto.getProxyPort()); + assertEquals("proxyserver" , dto.getProxyServer()); + assertEquals("trillian" , dto.getProxyUser()); + assertTrue(dto.isEnableProxy()); + assertEquals("description" , dto.getRealmDescription()); + assertTrue(dto.isEnableRepositoryArchive()); + assertTrue(dto.isDisableGroupingGrid()); + assertEquals("dd" , dto.getDateFormat()); + assertTrue(dto.isAnonymousAccessEnabled()); + assertTrue("adminGroups", dto.getAdminGroups().containsAll(Arrays.asList(expectedGroups))); + assertTrue("adminUsers", dto.getAdminUsers().containsAll(Arrays.asList(expectedUsers))); + assertEquals("baseurl" , dto.getBaseUrl()); + assertTrue(dto.isForceBaseUrl()); + assertEquals(1 , dto.getLoginAttemptLimit()); + assertTrue("proxyExcludes", dto.getProxyExcludes().containsAll(Arrays.asList(expectedExcludes))); + assertTrue(dto.isSkipFailedAuthenticators()); + assertEquals("pluginurl" , dto.getPluginUrl()); + assertEquals(2 , dto.getLoginAttemptLimitTimeout()); + assertTrue(dto.isEnabledXsrfProtection()); + + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref()); + } + + @Test + public void shouldMapFieldsWithoutUpdate() { + ScmConfiguration config = createConfiguration(); + + when(subject.hasRole("configuration:write:global")).thenReturn(false); + ConfigDto dto = mapper.map(config); + + assertEquals("baseurl", dto.getBaseUrl()); + assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref()); + assertFalse(dto.getLinks().hasLink("update")); + } + + private ScmConfiguration createConfiguration() { + ScmConfiguration config = new ScmConfiguration(); + config.setProxyPassword("heartOfGold"); + config.setProxyPort(1234); + config.setProxyServer("proxyserver"); + config.setProxyUser("trillian"); + config.setEnableProxy(true); + config.setRealmDescription("description"); + config.setEnableRepositoryArchive(true); + config.setDisableGroupingGrid(true); + config.setDateFormat("dd"); + config.setAnonymousAccessEnabled(true); + config.setAdminGroups(Sets.newSet(expectedGroups)); + config.setAdminUsers(Sets.newSet(expectedUsers)); + config.setBaseUrl("baseurl"); + config.setForceBaseUrl(true); + config.setLoginAttemptLimit(1); + config.setProxyExcludes(Sets.newSet(expectedExcludes)); + config.setSkipFailedAuthenticators(true); + config.setPluginUrl("pluginurl"); + config.setLoginAttemptLimitTimeout(2); + config.setEnabledXsrfProtection(true); + return config; + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapperTest.java index 4d2bf7c62e..6ca0ddec4a 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserCollectionToDtoMapperTest.java @@ -18,9 +18,7 @@ import java.util.Arrays; import java.util.List; import static java.util.stream.Collectors.toList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import static sonia.scm.PageResult.createPage; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java index 049d8ab1c8..1eee9b2270 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/UserRootResourceTest.java @@ -27,15 +27,10 @@ import java.net.URISyntaxException; import java.net.URL; import static java.util.Collections.singletonList; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; @SubjectAware( diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java index 02b25c0735..4c42ceb1ea 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -41,12 +41,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.invocation.InvocationOnMock; -import sonia.scm.HandlerEventType; -import sonia.scm.Manager; -import sonia.scm.ManagerTestBase; -import sonia.scm.ModelObject; -import sonia.scm.Type; -import sonia.scm.TypedObject; +import sonia.scm.*; import sonia.scm.config.ScmConfiguration; import sonia.scm.event.ScmEventBus; import sonia.scm.repository.api.HookContext; @@ -59,23 +54,10 @@ import sonia.scm.security.KeyGenerator; import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.JAXBConfigurationStoreFactory; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.Stack; +import java.util.*; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasProperty; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -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.assertTrue; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; diff --git a/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-update.json b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-update.json new file mode 100644 index 0000000000..bc45315a0c --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/api/v2/config-test-update.json @@ -0,0 +1,3 @@ +{ + "proxyPassword": "newPassword" +} diff --git a/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini b/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini new file mode 100644 index 0000000000..8647142b19 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/configuration/shiro.ini @@ -0,0 +1,9 @@ +[users] +readOnly = secret, reader +writeOnly = secret, writer +readWrite = secret, readerWriter + +[roles] +reader = configuration:read +writer = configuration:write +readerWriter = configuration:*