From 731337f2ab9c50b86b3c9d87b6f18c39c5d6254d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 16 Feb 2017 22:15:36 +0100 Subject: [PATCH 01/56] created adapter between scm and shiro caches, see issue #781 --- .../java/sonia/scm/cache/GuavaBaseCache.java | 113 +++++++++ .../main/java/sonia/scm/cache/GuavaCache.java | 191 +-------------- .../sonia/scm/cache/GuavaCacheManager.java | 115 ++++----- .../sonia/scm/cache/GuavaSecurityCache.java | 83 +++++++ .../scm/security/AuthorizationCollector.java | 2 +- .../sonia/scm/cache/CacheManagerTestBase.java | 3 +- .../scm/cache/GuavaCacheManagerTest.java | 12 + .../DefaultRepositoryManagerPerfTest.java | 223 ++++++++++++++++++ 8 files changed, 492 insertions(+), 250 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/cache/GuavaBaseCache.java create mode 100644 scm-webapp/src/main/java/sonia/scm/cache/GuavaSecurityCache.java create mode 100644 scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaBaseCache.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaBaseCache.java new file mode 100644 index 0000000000..24f7b05ea7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaBaseCache.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.cache; + +import com.google.common.cache.Cache; +import com.google.common.collect.Sets; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.Filter; + +/** + * Implementation of basic cache methods for guava based caches. + * + * @author Sebastian Sdorra + * + * @since 1.52 + * + * @param key + * @param value + */ +public abstract class GuavaBaseCache { + + /** + * the logger for GuavaBaseCache + */ + private static final Logger logger = LoggerFactory.getLogger(GuavaCache.class); + + protected com.google.common.cache.Cache cache; + protected CopyStrategy copyStrategy = CopyStrategy.NONE; + private final String name; + + GuavaBaseCache(com.google.common.cache.Cache cache, CopyStrategy copyStrategy, String name) { + this.cache = cache; + this.name = name; + + if (copyStrategy != null) { + this.copyStrategy = copyStrategy; + } + } + + //~--- methods -------------------------------------------------------------- + + public void clear() { + logger.debug("clear cache {}", name); + cache.invalidateAll(); + } + + public boolean contains(K key) { + return cache.getIfPresent(key) != null; + } + + public boolean removeAll(Filter filter) { + Set keysToRemove = Sets.newHashSet(); + + for (K key : cache.asMap().keySet()) { + if (filter.accept(key)) { + keysToRemove.add(key); + } + } + + boolean result = false; + + if (!keysToRemove.isEmpty()) { + cache.invalidateAll(keysToRemove); + result = true; + } + + return result; + } + + public V get(K key) { + V value = cache.getIfPresent(key); + + if (value != null) { + value = copyStrategy.copyOnRead(value); + } + + return value; + } + + public Cache getWrappedCache() { + return cache; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java index 9c761fd620..6481936d65 100644 --- a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCache.java @@ -34,18 +34,6 @@ package sonia.scm.cache; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Sets; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.Filter; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Set; - /** * * @author Sebastian Sdorra @@ -53,185 +41,22 @@ import java.util.Set; * @param * @param */ -public class GuavaCache implements Cache -{ +public class GuavaCache extends GuavaBaseCache implements Cache { - /** - * the logger for GuavaCache - */ - private static final Logger logger = - LoggerFactory.getLogger(GuavaCache.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param configuration - */ - public GuavaCache(GuavaNamedCacheConfiguration configuration) - { - this(configuration, configuration.getName()); + GuavaCache(com.google.common.cache.Cache cache, CopyStrategy copyStrategy, String name) { + super(cache, copyStrategy, name); } - - /** - * Constructs ... - * - * - * @param configuration - * @param name - */ - public GuavaCache(GuavaCacheConfiguration configuration, String name) - { - this(GuavaCaches.create(configuration, name), - configuration.getCopyStrategy(), name); - } - - /** - * Constructs ... - * - * - * @param cache - * @param copyStrategy - * @param name - */ - @VisibleForTesting - protected GuavaCache(com.google.common.cache.Cache cache, - CopyStrategy copyStrategy, String name) - { - this.cache = cache; - this.name = name; - - if (copyStrategy != null) - { - this.copyStrategy = copyStrategy; - } - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - */ + @Override - public void clear() - { - if (logger.isDebugEnabled()) - { - logger.debug("clear cache {}", name); - } - - cache.invalidateAll(); - } - - /** - * Method description - * - * - * @param key - * - * @return - */ - @Override - public boolean contains(K key) - { - return cache.getIfPresent(key) != null; - } - - /** - * Method description - * - * - * @param key - * @param value - */ - @Override - public void put(K key, V value) - { + public void put(K key, V value) { cache.put(key, copyStrategy.copyOnWrite(value)); } - /** - * Method description - * - * - * @param key - * - * @return - */ @Override - public boolean remove(K key) - { + public boolean remove(K key) { cache.invalidate(key); return true; - } - - /** - * Method description - * - * - * @param filter - * - * @return - */ - @Override - public boolean removeAll(Filter filter) - { - Set keysToRemove = Sets.newHashSet(); - - for (K key : cache.asMap().keySet()) - { - if (filter.accept(key)) - { - keysToRemove.add(key); - } - } - - boolean result = false; - - if (!keysToRemove.isEmpty()) - { - cache.invalidateAll(keysToRemove); - result = true; - } - - return result; - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param key - * - * @return - */ - @Override - public V get(K key) - { - V value = cache.getIfPresent(key); - - if (value != null) - { - value = copyStrategy.copyOnRead(value); - } - - return value; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private com.google.common.cache.Cache cache; - - /** Field description */ - private CopyStrategy copyStrategy = CopyStrategy.NONE; - - /** Field description */ - private String name; + } + } diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java index 87fe2d9d15..fb112c0107 100644 --- a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java @@ -47,66 +47,52 @@ import java.io.IOException; import java.util.Map; +import org.apache.shiro.cache.CacheException; + /** - * + * Guava based implementation of {@link CacheManager} and {@link org.apache.shiro.cache.CacheManager}. + * * @author Sebastian Sdorra */ @Singleton -public class GuavaCacheManager implements CacheManager +public class GuavaCacheManager implements CacheManager, org.apache.shiro.cache.CacheManager { /** * the logger for GuavaCacheManager */ - private static final Logger logger = - LoggerFactory.getLogger(GuavaCacheManager.class); + private static final Logger logger = LoggerFactory.getLogger(GuavaCacheManager.class); + private volatile Map cacheMap = Maps.newHashMap(); + + private GuavaCacheConfiguration defaultConfiguration; + + //~--- constructors --------------------------------------------------------- - /** - * Constructs ... - * - */ - public GuavaCacheManager() - { + public GuavaCacheManager() { this(GuavaCacheConfigurationReader.read()); } - /** - * Constructs ... - * - * - * @param config - */ @VisibleForTesting - protected GuavaCacheManager(GuavaCacheManagerConfiguration config) - { + protected GuavaCacheManager(GuavaCacheManagerConfiguration config) { defaultConfiguration = config.getDefaultCache(); - for (GuavaNamedCacheConfiguration ncc : config.getCaches()) - { - logger.debug("create cache {} from configured configuration {}", - ncc.getName(), ncc); - cacheMap.put(ncc.getName(), new GuavaCache(ncc)); + for (GuavaNamedCacheConfiguration ncc : config.getCaches()) { + logger.debug("create cache {} from configured configuration {}", ncc.getName(), ncc); + cacheMap.put(ncc.getName(), new CacheWithConfiguration( + GuavaCaches.create(defaultConfiguration, ncc.getName()), + defaultConfiguration) + ); } } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @throws IOException - */ @Override - public void close() throws IOException - { + public void close() throws IOException { logger.info("close guava cache manager"); - for (Cache c : cacheMap.values()) - { - c.clear(); + for (CacheWithConfiguration c : cacheMap.values()) { + c.cache.invalidateAll(); } cacheMap.clear(); @@ -114,43 +100,44 @@ public class GuavaCacheManager implements CacheManager //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @param key - * @param value - * @param name - * @param - * @param - * - * @return - */ - @Override - public synchronized GuavaCache getCache(Class key, - Class value, String name) - { + private synchronized CacheWithConfiguration getCacheWithConfiguration(String name) { logger.trace("try to retrieve cache {}", name); - GuavaCache cache = cacheMap.get(name); + CacheWithConfiguration cache = cacheMap.get(name); - if (cache == null) - { + if (cache == null) { logger.debug( "cache {} does not exists, creating a new instance from default configuration: {}", - name, defaultConfiguration); - cache = new GuavaCache(defaultConfiguration, name); + name, defaultConfiguration + ); + cache = new CacheWithConfiguration(GuavaCaches.create(defaultConfiguration, name), defaultConfiguration); cacheMap.put(name, cache); } - + return cache; } + + @Override + public GuavaCache getCache(Class key, Class value, String name) { + CacheWithConfiguration cw = getCacheWithConfiguration(name); + return new GuavaCache<>(cw.cache, cw.configuration.getCopyStrategy(), name); + } + + @Override + public GuavaSecurityCache getCache(String name) throws CacheException { + CacheWithConfiguration cw = getCacheWithConfiguration(name); + return new GuavaSecurityCache<>(cw.cache, cw.configuration.getCopyStrategy(), name); + } - //~--- fields --------------------------------------------------------------- + private static class CacheWithConfiguration { + + private final com.google.common.cache.Cache cache; + private final GuavaCacheConfiguration configuration; - /** Field description */ - private volatile Map cacheMap = Maps.newHashMap(); - - /** Field description */ - private GuavaCacheConfiguration defaultConfiguration; + private CacheWithConfiguration(com.google.common.cache.Cache cache, GuavaCacheConfiguration configuration) { + this.cache = cache; + this.configuration = configuration; + } + + } } diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaSecurityCache.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaSecurityCache.java new file mode 100644 index 0000000000..679110eb65 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaSecurityCache.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.cache; + +import com.google.common.cache.Cache; +import java.util.Collection; +import java.util.Set; +import org.apache.shiro.cache.CacheException; + +/** + * Guava based implementation of {@link org.apache.shiro.cache.Cache}. + * + * @author Sebastian Sdorra + * + * @since 1.52 + * + * @param + * @param + */ +public class GuavaSecurityCache extends GuavaBaseCache implements org.apache.shiro.cache.Cache { + + GuavaSecurityCache(Cache cache, CopyStrategy copyStrategy, String name) { + super(cache, copyStrategy, name); + } + + @Override + public V put(K key, V value) throws CacheException { + V previousValue = cache.getIfPresent(key); + cache.put(key, value); + return previousValue; + } + + @Override + public V remove(K key) throws CacheException { + V previousValue = cache.getIfPresent(key); + cache.invalidate(key); + return previousValue; + } + + @Override + public int size() { + return (int) cache.size(); + } + + @Override + public Set keys() { + return cache.asMap().keySet(); + } + + @Override + public Collection values() { + return cache.asMap().values(); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationCollector.java index 315f19d1e5..cbc22ee553 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationCollector.java @@ -333,7 +333,7 @@ public class AuthorizationCollector * * @return */ - AuthorizationInfo collect(PrincipalCollection principals) + public AuthorizationInfo collect(PrincipalCollection principals) { Preconditions.checkNotNull(principals, "principals parameter is required"); diff --git a/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java b/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java index d2290c8143..7332ecb39b 100644 --- a/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java +++ b/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java @@ -159,8 +159,7 @@ public abstract class CacheManagerTestBase * @param c1 * @param c2 */ - protected void assertIsSame(Cache c1, - Cache c2) + protected void assertIsSame(Cache c1, Cache c2) { assertSame(c1, c2); } diff --git a/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheManagerTest.java b/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheManagerTest.java index 2c1e991c49..3d91a56f40 100644 --- a/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/cache/GuavaCacheManagerTest.java @@ -32,6 +32,8 @@ package sonia.scm.cache; +import org.junit.Assert; + /** * * @author Sebastian Sdorra @@ -50,4 +52,14 @@ public class GuavaCacheManagerTest extends CacheManagerTestBase { return CacheTestUtil.createDefaultGuavaCacheManager(); } + + @Override + protected void assertIsSame(Cache c1, Cache c2) { + Assert.assertSame( + ((GuavaCache)c1).getWrappedCache(), + ((GuavaCache)c2).getWrappedCache() + ); + } + + } diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java new file mode 100644 index 0000000000..9fb78f5879 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.repository; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Provider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.mgt.DefaultSecurityManager; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; +import org.apache.shiro.util.ThreadContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.SCMContextProvider; +import sonia.scm.Type; +import sonia.scm.cache.GuavaCacheManager; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.AuthorizationCollector; +import sonia.scm.security.DefaultKeyGenerator; +import sonia.scm.security.KeyGenerator; +import sonia.scm.security.RepositoryPermissionResolver; +import sonia.scm.security.SecuritySystem; +import sonia.scm.user.UserTestData; + +/** + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class DefaultRepositoryManagerPerfTest { + + private static final int REPOSITORY_COUNT = 2000; + + private static final String REPOSITORY_TYPE = "perf"; + + @Mock + private SCMContextProvider contextProvider; + + @Mock + private RepositoryDAO repositoryDAO; + + @Mock + private PreProcessorUtil preProcessorUtil; + + private final ScmConfiguration configuration = new ScmConfiguration(); + + private final KeyGenerator keyGenerator = new DefaultKeyGenerator(); + + @Mock + private RepositoryHandler repositoryHandler; + + private DefaultRepositoryManager repositoryManager; + + @Mock + private SecuritySystem securitySystem; + + private AuthorizationCollector authzCollector; + + @Before + public void setUpObjectUnderTest(){ + when(repositoryHandler.getType()).thenReturn(new Type(REPOSITORY_TYPE, REPOSITORY_TYPE)); + Set handlerSet = ImmutableSet.of(repositoryHandler); + Provider> repositoryListenersProvider = new SetProvider(); + Provider> repositoryHooksProvider = new SetProvider(); + + repositoryManager = new DefaultRepositoryManager( + configuration, + contextProvider, + keyGenerator, + repositoryDAO, + handlerSet, + repositoryListenersProvider, + repositoryHooksProvider, + preProcessorUtil + ); + + setUpTestRepositories(); + + GuavaCacheManager cacheManager = new GuavaCacheManager(); + authzCollector = new AuthorizationCollector(cacheManager, repositoryDAO, securitySystem, new RepositoryPermissionResolver()); + DefaultSecurityManager securityManager = new DefaultSecurityManager(new DummyRealm(authzCollector, cacheManager)); + + ThreadContext.bind(securityManager); + } + + @After + public void tearDown(){ + ThreadContext.unbindSecurityManager(); + } + + @Test(timeout = 6000l) + public void perfTestGetAll(){ + SecurityUtils.getSubject().login(new UsernamePasswordToken("trillian", "secret")); + + List times = new ArrayList<>(); + for ( int i=0; i<3; i++ ) { + times.add(benchGetAll()); + } + + long average = calculateAverage(times); + double value = (double) average / TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS); + + // Too bad this functionality is not exposed as a regular method call + System.out.println( String.format("%.4g s", value) ); + } + +private long calculateAverage(List times) { + Long sum = 0l; + if(!times.isEmpty()) { + for (Long time : times) { + sum += time; + } + return Math.round(sum.doubleValue() / times.size()); + } + return sum; +} + + private long benchGetAll(){ + Stopwatch sw = Stopwatch.createStarted(); + System.out.append("found ").append(String.valueOf(repositoryManager.getAll().size())); + sw.stop(); + System.out.append(" in ").println(sw); + return sw.elapsed(TimeUnit.MILLISECONDS); + } + + private void setUpTestRepositories() { + Map repositories = new LinkedHashMap<>(); + for ( int i=0; i Date: Fri, 17 Feb 2017 21:36:52 +0100 Subject: [PATCH 02/56] start refactoring of ScmRealm to simplify shiro cache integration --- .../security/AuthenticationInfoCollector.java | 324 +++++++++++ .../scm/security/AuthenticatorFacade.java | 78 +++ .../sonia/scm/security/GroupCollector.java | 55 ++ .../java/sonia/scm/security/ScmRealm.java | 520 ++---------------- .../java/sonia/scm/security/ScmRealmTest.java | 23 +- 5 files changed, 517 insertions(+), 483 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/AuthenticatorFacade.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java b/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java new file mode 100644 index 0000000000..7548c870f1 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java @@ -0,0 +1,324 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import com.google.common.base.Joiner; +import com.google.common.collect.Sets; +import com.google.inject.Inject; +import com.google.inject.Provider; +import java.io.IOException; +import java.util.Collection; +import java.util.Set; +import javax.servlet.http.HttpServletRequest; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.DisabledAccountException; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.SimplePrincipalCollection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.HandlerEvent; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.group.GroupNames; +import sonia.scm.user.User; +import sonia.scm.user.UserDAO; +import sonia.scm.user.UserEventHack; +import sonia.scm.user.UserException; +import sonia.scm.user.UserManager; +import sonia.scm.util.Util; +import sonia.scm.web.security.AuthenticationResult; + +/** + * + * @author Sebastian Sdorra + */ +public class AuthenticationInfoCollector { + + private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; + + /** + * the logger for AuthenticationInfoCollector + */ + private static final Logger logger = LoggerFactory.getLogger(AuthenticationInfoCollector.class); + + private final ScmConfiguration configuration; + private final UserManager userManager; + private final GroupManager groupManager; + private final UserDAO userDAO; + private final Provider requestProvider; + + @Inject + public AuthenticationInfoCollector(ScmConfiguration configuration, UserManager userManager, GroupManager groupManager, + UserDAO userDAO, Provider requestProvider) { + this.configuration = configuration; + this.userManager = userManager; + this.groupManager = groupManager; + this.userDAO = userDAO; + this.requestProvider = requestProvider; + } + + AuthenticationInfo createAuthenticationInfo(UsernamePasswordToken token, AuthenticationResult result) { + User user = result.getUser(); + Collection groups = authenticate(requestProvider.get(), new String(token.getPassword()), result); + + SimplePrincipalCollection collection = new SimplePrincipalCollection(); + + /* + * the first (primary) principal should be a unique identifier + */ + collection.add(user.getId(), ScmRealm.NAME); + collection.add(user, ScmRealm.NAME); + collection.add(new GroupNames(groups), ScmRealm.NAME); + + return new SimpleAuthenticationInfo(collection, token.getPassword()); + } + + + private Set authenticate(HttpServletRequest request, String password, AuthenticationResult ar) { + Set groupSet = null; + User user = ar.getUser(); + + try + { + groupSet = createGroupSet(ar); + + // check for admin user + checkForAuthenticatedAdmin(user, groupSet); + + // store user + User dbUser = userDAO.get(user.getName()); + + if (dbUser != null) + { + checkDBForAdmin(user, dbUser); + checkDBForActive(user, dbUser); + } + + // create new user + else if (user.isValid()) + { + user.setCreationDate(System.currentTimeMillis()); + + // TODO find a better way + UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_CREATE); + userDAO.add(user); + UserEventHack.fireEvent(userManager, user, HandlerEvent.CREATE); + } + else if (logger.isErrorEnabled()) + { + logger.error("could not create user {}, beacause it is not valid", + user.getName()); + } + + if (user.isActive()) + { + + if (logger.isDebugEnabled()) + { + logGroups(user, groupSet); + } + + // store encrypted credentials in session + String credentials = user.getName(); + + if (Util.isNotEmpty(password)) + { + credentials = credentials.concat(":").concat(password); + } + + credentials = CipherUtil.getInstance().encode(credentials); + request.getSession(true).setAttribute(SCM_CREDENTIALS, credentials); + } + else + { + + String msg = "user ".concat(user.getName()).concat(" is deactivated"); + + if (logger.isWarnEnabled()) + { + logger.warn(msg); + } + + throw new DisabledAccountException(msg); + + } + } + catch (IOException | UserException ex) + { + logger.error("authentication failed", ex); + + throw new AuthenticationException("authentication failed", ex); + } + + return groupSet; + } + + private void checkDBForActive(User user, User dbUser) + { + // user is deactivated by database + if (!dbUser.isActive()) + { + if (logger.isDebugEnabled()) + { + logger.debug("user {} is marked as deactivated by local database", + user.getName()); + } + + user.setActive(false); + } + } + + private void checkDBForAdmin(User user, User dbUser) throws UserException, IOException + { + + // if database user is an admin, set admin for the current user + if (dbUser.isAdmin()) + { + if (logger.isDebugEnabled()) + { + logger.debug("user {} of type {} is marked as admin by local database", + user.getName(), user.getType()); + } + + user.setAdmin(true); + } + + // modify existing user, copy properties except password and admin + if (user.copyProperties(dbUser, false)) + { + user.setLastModified(System.currentTimeMillis()); + UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_MODIFY); + userDAO.modify(user); + UserEventHack.fireEvent(userManager, user, HandlerEvent.MODIFY); + } + } + + private void checkForAuthenticatedAdmin(User user, Set groupSet) + { + if (!user.isAdmin()) + { + user.setAdmin(isAdmin(user, groupSet)); + + if (logger.isDebugEnabled() && user.isAdmin()) + { + logger.debug("user {} is marked as admin by configuration", + user.getName()); + } + } + else if (logger.isDebugEnabled()) + { + logger.debug("authenticator {} marked user {} as admin", user.getType(), + user.getName()); + } + } + + private Set createGroupSet(AuthenticationResult ar) + { + Set groupSet = Sets.newHashSet(); + + // add group for all authenticated users + groupSet.add(GroupNames.AUTHENTICATED); + + // load external groups + Collection extGroups = ar.getGroups(); + + if (extGroups != null) + { + groupSet.addAll(extGroups); + } + + // load internal groups + loadGroups(ar.getUser(), groupSet); + + return groupSet; + } + + private void loadGroups(User user, Set groupSet) + { + Collection groupCollection = groupManager.getGroupsForMember(user.getName()); + + if (groupCollection != null) + { + for (Group group : groupCollection) + { + groupSet.add(group.getName()); + } + } + } + + private void logGroups(User user, Set groups) + { + StringBuilder msg = new StringBuilder("user "); + + msg.append(user.getName()); + + if (Util.isNotEmpty(groups)) + { + msg.append(" is member of "); + + Joiner.on(", ").appendTo(msg, groups); + } + else + { + msg.append(" is not a member of a group"); + } + + logger.debug(msg.toString()); + } + + private boolean isAdmin(User user, Collection groups) + { + boolean result = false; + Set adminUsers = configuration.getAdminUsers(); + + if (adminUsers != null) + { + result = adminUsers.contains(user.getName()); + } + + if (!result) + { + Set adminGroups = configuration.getAdminGroups(); + + if (adminGroups != null) + { + result = Util.containsOne(adminGroups, groups); + } + } + + return result; + } + + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthenticatorFacade.java b/scm-webapp/src/main/java/sonia/scm/security/AuthenticatorFacade.java new file mode 100644 index 0000000000..ee6d4db4e9 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthenticatorFacade.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.shiro.authc.UsernamePasswordToken; +import sonia.scm.web.security.AuthenticationManager; +import sonia.scm.web.security.AuthenticationResult; + +/** + * Facade for the authentication process. The main reason for this facade is to reduce the number of constructor + * parameters on the realm. This should improve testability. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +public class AuthenticatorFacade { + + private final AuthenticationManager authenticator; + private final Provider requestProvider; + private final Provider responseProvider; + + @Inject + public AuthenticatorFacade(AuthenticationManager authenticator, Provider requestProvider, + Provider responseProvider) { + this.authenticator = authenticator; + this.requestProvider = requestProvider; + this.responseProvider = responseProvider; + } + + /** + * Delegates the authentication request to the injected implementation of the {@link AuthenticationManager}. + * + * @param token username password token + * + * @return authentication result + */ + public AuthenticationResult authenticate(UsernamePasswordToken token) { + return authenticator.authenticate( + requestProvider.get(), + responseProvider.get(), + token.getUsername(), + new String(token.getPassword()) + ); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java b/scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java new file mode 100644 index 0000000000..616351107e --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import com.google.inject.Inject; +import java.util.Set; +import sonia.scm.group.GroupManager; +import sonia.scm.web.security.AuthenticationResult; + +/** + * + * @author Sebastian Sdorra + */ +public class GroupCollector { + + private final GroupManager groupManager; + + @Inject + public GroupCollector(GroupManager groupManager) { + this.groupManager = groupManager; + } + + public Set collectGroups(AuthenticationResult result){ + + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java index 15687cd63f..b49c9a6f3c 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java @@ -35,107 +35,58 @@ package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Joiner; -import com.google.common.collect.Sets; import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; import org.apache.shiro.authc.AccountException; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.DisabledAccountException; -import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.pam.UnsupportedTokenException; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; -import org.apache.shiro.subject.SimplePrincipalCollection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sonia.scm.HandlerEvent; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.group.Group; -import sonia.scm.group.GroupManager; -import sonia.scm.group.GroupNames; -import sonia.scm.repository.RepositoryManager; -import sonia.scm.user.User; -import sonia.scm.user.UserDAO; -import sonia.scm.user.UserEventHack; -import sonia.scm.user.UserException; -import sonia.scm.user.UserManager; -import sonia.scm.util.Util; -import sonia.scm.web.security.AuthenticationManager; import sonia.scm.web.security.AuthenticationResult; import sonia.scm.web.security.AuthenticationState; //~--- JDK imports ------------------------------------------------------------ -import java.io.IOException; - -import java.util.Collection; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; /** - * + * SCM-Manager authentication realm. + * * @author Sebastian Sdorra */ @Singleton -public class ScmRealm extends AuthorizingRealm -{ - - /** Field description */ +public class ScmRealm extends AuthorizingRealm { + public static final String NAME = "scm"; + - /** Field description */ - private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; - + private final AuthenticatorFacade authenticator; + private final LoginAttemptHandler loginAttemptHandler; + private final AuthenticationInfoCollector authcCollector; + private final AuthorizationCollector authzCollector; + /** - * the logger for ScmRealm - */ - private static final Logger logger = LoggerFactory.getLogger(ScmRealm.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... + * Constructs a new scm realm. * - * @param configuration - * @param loginAttemptHandler - * @param collector - * @param userManager - * @param groupManager - * @param userDAO - * @param authenticator - * @param manager - * @param requestProvider - * @param responseProvider + * @param authenticator authenticator facade + * @param loginAttemptHandler login attempt handler + * @param authcCollector authentication info collector + * @param authzCollector authorization collector */ @Inject - public ScmRealm(ScmConfiguration configuration, - LoginAttemptHandler loginAttemptHandler, AuthorizationCollector collector, - UserManager userManager, GroupManager groupManager, UserDAO userDAO, - AuthenticationManager authenticator, RepositoryManager manager, - Provider requestProvider, - Provider responseProvider) - { - this.configuration = configuration; - this.loginAttemptHandler = loginAttemptHandler; - this.collector = collector; - this.userManager = userManager; - this.groupManager = groupManager; - this.userDAO = userDAO; + public ScmRealm(AuthenticatorFacade authenticator, LoginAttemptHandler loginAttemptHandler, + AuthenticationInfoCollector authcCollector, AuthorizationCollector authzCollector) { this.authenticator = authenticator; - this.requestProvider = requestProvider; - this.responseProvider = responseProvider; + this.loginAttemptHandler = loginAttemptHandler; + this.authcCollector = authcCollector; + this.authzCollector = authzCollector; + // set token class setAuthenticationTokenClass(UsernamePasswordToken.class); @@ -149,417 +100,42 @@ public class ScmRealm extends AuthorizingRealm setPermissionResolver(new RepositoryPermissionResolver()); } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * - * @param authToken - * - * @return - * - * @throws AuthenticationException - */ @Override - protected AuthenticationInfo doGetAuthenticationInfo( - AuthenticationToken authToken) - throws AuthenticationException - { - if (!(authToken instanceof UsernamePasswordToken)) - { - throw new UnsupportedTokenException("ScmAuthenticationToken is required"); - } + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException { + UsernamePasswordToken token = castToken(authToken); - loginAttemptHandler.beforeAuthentication(authToken); + loginAttemptHandler.beforeAuthentication(token); + AuthenticationResult result = authenticator.authenticate(token); - UsernamePasswordToken token = (UsernamePasswordToken) authToken; - - AuthenticationInfo info = null; - AuthenticationResult result = - authenticator.authenticate(requestProvider.get(), responseProvider.get(), - token.getUsername(), new String(token.getPassword())); - - if ((result != null) && (AuthenticationState.SUCCESS == result.getState())) - { - loginAttemptHandler.onSuccessfulAuthentication(authToken, result); - info = createAuthenticationInfo(token, result); - } - else if ((result != null) - && (AuthenticationState.NOT_FOUND == result.getState())) - { - throw new UnknownAccountException( - "unknown account ".concat(token.getUsername())); - } - else - { + if (isSuccessful(result)) { + loginAttemptHandler.onSuccessfulAuthentication(token, result); + return authcCollector.createAuthenticationInfo(token, result); + } else if (isAccountNotFound(result)) { + throw new UnknownAccountException("unknown account ".concat(token.getUsername())); + } else { loginAttemptHandler.onUnsuccessfulAuthentication(authToken, result); throw new AccountException("authentication failed"); } - - return info; + } + + private UsernamePasswordToken castToken(AuthenticationToken token) { + if (!(token instanceof UsernamePasswordToken)) { + throw new UnsupportedTokenException("UsernamePasswordToken is required"); + } + return (UsernamePasswordToken) token; + } + + private boolean isSuccessful(AuthenticationResult result) { + return result != null && AuthenticationState.SUCCESS == result.getState(); + } + + private boolean isAccountNotFound(AuthenticationResult result) { + return result != null && AuthenticationState.NOT_FOUND == result.getState(); } - /** - * Method description - * - * - * @param principals - * - * @return - */ @Override - protected AuthorizationInfo doGetAuthorizationInfo( - PrincipalCollection principals) - { - return collector.collect(principals); + protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){ + return authzCollector.collect(principals); } - - /** - * Method description - * - * - * @param request - * @param password - * @param ar - * - * @return - */ - private Set authenticate(HttpServletRequest request, String password, - AuthenticationResult ar) - { - Set groupSet = null; - User user = ar.getUser(); - - try - { - groupSet = createGroupSet(ar); - - // check for admin user - checkForAuthenticatedAdmin(user, groupSet); - - // store user - User dbUser = userDAO.get(user.getName()); - - if (dbUser != null) - { - checkDBForAdmin(user, dbUser); - checkDBForActive(user, dbUser); - } - - // create new user - else if (user.isValid()) - { - user.setCreationDate(System.currentTimeMillis()); - - // TODO find a better way - UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_CREATE); - userDAO.add(user); - UserEventHack.fireEvent(userManager, user, HandlerEvent.CREATE); - } - else if (logger.isErrorEnabled()) - { - logger.error("could not create user {}, beacause it is not valid", - user.getName()); - } - - if (user.isActive()) - { - - if (logger.isDebugEnabled()) - { - logGroups(user, groupSet); - } - - // store encrypted credentials in session - String credentials = user.getName(); - - if (Util.isNotEmpty(password)) - { - credentials = credentials.concat(":").concat(password); - } - - credentials = CipherUtil.getInstance().encode(credentials); - request.getSession(true).setAttribute(SCM_CREDENTIALS, credentials); - } - else - { - - String msg = "user ".concat(user.getName()).concat(" is deactivated"); - - if (logger.isWarnEnabled()) - { - logger.warn(msg); - } - - throw new DisabledAccountException(msg); - - } - } - catch (Exception ex) - { - logger.error("authentication failed", ex); - - throw new AuthenticationException("authentication failed", ex); - } - - return groupSet; - } - - /** - * Method description - * - * - * @param user - * @param dbUser - */ - private void checkDBForActive(User user, User dbUser) - { - - // user is deactivated by database - if (!dbUser.isActive()) - { - if (logger.isDebugEnabled()) - { - logger.debug("user {} is marked as deactivated by local database", - user.getName()); - } - - user.setActive(false); - } - } - - /** - * Method description - * - * - * @param user - * @param dbUser - * - * @throws IOException - * @throws UserException - */ - private void checkDBForAdmin(User user, User dbUser) - throws UserException, IOException - { - - // if database user is an admin, set admin for the current user - if (dbUser.isAdmin()) - { - if (logger.isDebugEnabled()) - { - logger.debug("user {} of type {} is marked as admin by local database", - user.getName(), user.getType()); - } - - user.setAdmin(true); - } - - // modify existing user, copy properties except password and admin - if (user.copyProperties(dbUser, false)) - { - user.setLastModified(System.currentTimeMillis()); - UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_MODIFY); - userDAO.modify(user); - UserEventHack.fireEvent(userManager, user, HandlerEvent.MODIFY); - } - } - - /** - * Method description - * - * - * @param user - * @param groupSet - */ - private void checkForAuthenticatedAdmin(User user, Set groupSet) - { - if (!user.isAdmin()) - { - user.setAdmin(isAdmin(user, groupSet)); - - if (logger.isDebugEnabled() && user.isAdmin()) - { - logger.debug("user {} is marked as admin by configuration", - user.getName()); - } - } - else if (logger.isDebugEnabled()) - { - logger.debug("authenticator {} marked user {} as admin", user.getType(), - user.getName()); - } - } - - /** - * Method description - * - * - * @param token - * @param result - * - * @return - */ - private AuthenticationInfo createAuthenticationInfo( - UsernamePasswordToken token, AuthenticationResult result) - { - User user = result.getUser(); - Collection groups = authenticate(requestProvider.get(), - new String(token.getPassword()), result); - - SimplePrincipalCollection collection = new SimplePrincipalCollection(); - - /* - * the first (primary) principal should be a unique identifier - */ - collection.add(user.getId(), NAME); - collection.add(user, NAME); - collection.add(new GroupNames(groups), NAME); - - return new SimpleAuthenticationInfo(collection, token.getPassword()); - } - - /** - * Method description - * - * - * @param ar - * - * @return - */ - private Set createGroupSet(AuthenticationResult ar) - { - Set groupSet = Sets.newHashSet(); - - // add group for all authenticated users - groupSet.add(GroupNames.AUTHENTICATED); - - // load external groups - Collection extGroups = ar.getGroups(); - - if (extGroups != null) - { - groupSet.addAll(extGroups); - } - - // load internal groups - loadGroups(ar.getUser(), groupSet); - - return groupSet; - } - - /** - * Method description - * - * - * - * @param user - * @param groupSet - */ - private void loadGroups(User user, Set groupSet) - { - Collection groupCollection = - groupManager.getGroupsForMember(user.getName()); - - if (groupCollection != null) - { - for (Group group : groupCollection) - { - groupSet.add(group.getName()); - } - } - } - - /** - * Method description - * - * - * @param user - * @param groups - */ - private void logGroups(User user, Set groups) - { - StringBuilder msg = new StringBuilder("user "); - - msg.append(user.getName()); - - if (Util.isNotEmpty(groups)) - { - msg.append(" is member of "); - - Joiner.on(", ").appendTo(msg, groups); - } - else - { - msg.append(" is not a member of a group"); - } - - logger.debug(msg.toString()); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * - * - * @param user - * @param groups - * @return - */ - private boolean isAdmin(User user, Collection groups) - { - boolean result = false; - Set adminUsers = configuration.getAdminUsers(); - - if (adminUsers != null) - { - result = adminUsers.contains(user.getName()); - } - - if (!result) - { - Set adminGroups = configuration.getAdminGroups(); - - if (adminGroups != null) - { - result = Util.containsOne(adminGroups, groups); - } - } - - return result; - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final AuthenticationManager authenticator; - - /** Field description */ - private final AuthorizationCollector collector; - - /** Field description */ - private final ScmConfiguration configuration; - - /** Field description */ - private final GroupManager groupManager; - - /** Field description */ - private final LoginAttemptHandler loginAttemptHandler; - - /** Field description */ - private final Provider requestProvider; - - /** Field description */ - private final Provider responseProvider; - - /** Field description */ - private final UserDAO userDAO; - - /** Field description */ - private final UserManager userManager; } diff --git a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java index f8c7efbe3e..692a9493c7 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java @@ -479,7 +479,15 @@ public class ScmRealmTest CacheManager cacheManager = new MapCacheManager(); - AuthorizationCollector collector = new AuthorizationCollector( + AuthenticationInfoCollector authcCollector = new AuthenticationInfoCollector( + new ScmConfiguration(), + userManager, + groupManager, + userDAO, + requestProvider + ); + + AuthorizationCollector authzCollector = new AuthorizationCollector( cacheManager, repositoryDAO, securitySystem, @@ -502,17 +510,10 @@ public class ScmRealmTest }; return new ScmRealm( - new ScmConfiguration(), + new AuthenticatorFacade(authManager, requestProvider, responseProvider), dummyLoginAttemptHandler, - collector, - // cacheManager, - userManager, - groupManager, - userDAO, - authManager, - null, - requestProvider, - responseProvider + authcCollector, + authzCollector ); //J+ } From b6a49570cf11e306b8badab3e69dd43eff78171c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 17 Feb 2017 23:06:05 +0100 Subject: [PATCH 03/56] refactor AuthenticationInfoCollector --- .../sonia/scm/security/AdminDetector.java | 105 ++++++ .../security/AuthenticationInfoCollector.java | 314 ++++-------------- .../sonia/scm/security/GroupCollector.java | 72 +++- .../security/LocalDatabaseSynchronizer.java | 127 +++++++ .../java/sonia/scm/security/SessionStore.java | 66 ++++ .../java/sonia/scm/security/ScmRealmTest.java | 16 +- 6 files changed, 436 insertions(+), 264 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/LocalDatabaseSynchronizer.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/SessionStore.java diff --git a/scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java b/scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java new file mode 100644 index 0000000000..ddc43d8b0a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import com.google.inject.Inject; +import java.util.Collection; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.user.User; +import sonia.scm.util.Util; + +/** + * Detects administrator from configuration. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +public class AdminDetector { + + /** + * the logger for AdminDetector + */ + private static final Logger LOG = LoggerFactory.getLogger(AdminDetector.class); + + private final ScmConfiguration configuration; + + /** + * Constructs admin detector. + * + * @param configuration scm-manager main configuration + */ + @Inject + public AdminDetector(ScmConfiguration configuration) { + this.configuration = configuration; + } + + /** + * Checks is the authenticated user is marked as administrator by {@link ScmConfiguration}. + * + * @param user authenticated user + * @param groups groups of authenticated user + */ + public void checkForAuthenticatedAdmin(User user, Set groups) { + if (!user.isAdmin()) { + user.setAdmin(isAdminByConfiguration(user, groups)); + + if (LOG.isDebugEnabled() && user.isAdmin()) { + LOG.debug("user {} is marked as admin by configuration", user.getName()); + } + } + else if (LOG.isDebugEnabled()) { + LOG.debug("authenticator {} marked user {} as admin", user.getType(), user.getName()); + } + } + + private boolean isAdminByConfiguration(User user, Collection groups) { + boolean result = false; + Set adminUsers = configuration.getAdminUsers(); + + if (adminUsers != null) { + result = adminUsers.contains(user.getName()); + } + + if (!result) { + Set adminGroups = configuration.getAdminGroups(); + + if (adminGroups != null) { + result = Util.containsOne(adminGroups, groups); + } + } + + return result; + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java b/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java index 7548c870f1..8eb4de4c48 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java @@ -30,295 +30,95 @@ */ package sonia.scm.security; -import com.google.common.base.Joiner; -import com.google.common.collect.Sets; import com.google.inject.Inject; -import com.google.inject.Provider; -import java.io.IOException; -import java.util.Collection; import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.DisabledAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.HandlerEvent; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.group.Group; -import sonia.scm.group.GroupManager; import sonia.scm.group.GroupNames; import sonia.scm.user.User; -import sonia.scm.user.UserDAO; -import sonia.scm.user.UserEventHack; -import sonia.scm.user.UserException; -import sonia.scm.user.UserManager; -import sonia.scm.util.Util; import sonia.scm.web.security.AuthenticationResult; /** - * + * Collects authentication info for realm. + * * @author Sebastian Sdorra + * @since 1.52 */ public class AuthenticationInfoCollector { - - private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; /** * the logger for AuthenticationInfoCollector */ - private static final Logger logger = LoggerFactory.getLogger(AuthenticationInfoCollector.class); + private static final Logger LOG = LoggerFactory.getLogger(AuthenticationInfoCollector.class); - private final ScmConfiguration configuration; - private final UserManager userManager; - private final GroupManager groupManager; - private final UserDAO userDAO; - private final Provider requestProvider; + private final LocalDatabaseSynchronizer synchronizer; + private final GroupCollector groupCollector; + private final SessionStore sessionStore; + /** + * Construct a new AuthenticationInfoCollector. + * + * @param synchronizer local database synchronizer + * @param groupCollector groups collector + * @param sessionStore session store + */ @Inject - public AuthenticationInfoCollector(ScmConfiguration configuration, UserManager userManager, GroupManager groupManager, - UserDAO userDAO, Provider requestProvider) { - this.configuration = configuration; - this.userManager = userManager; - this.groupManager = groupManager; - this.userDAO = userDAO; - this.requestProvider = requestProvider; + public AuthenticationInfoCollector( + LocalDatabaseSynchronizer synchronizer, GroupCollector groupCollector, SessionStore sessionStore + ) { + this.synchronizer = synchronizer; + this.groupCollector = groupCollector; + this.sessionStore = sessionStore; } - AuthenticationInfo createAuthenticationInfo(UsernamePasswordToken token, AuthenticationResult result) { - User user = result.getUser(); - Collection groups = authenticate(requestProvider.get(), new String(token.getPassword()), result); - + /** + * Creates authentication info from token and authentication result. + * + * @param token username and password token + * @param authenticationResult authentication result + * + * @return authentication info + */ + public AuthenticationInfo createAuthenticationInfo( + UsernamePasswordToken token, AuthenticationResult authenticationResult + ) { + User user = authenticationResult.getUser(); + Set groups = groupCollector.collectGroups(authenticationResult); + + synchronizer.synchronize(user, groups); + + if (isUserIsDisabled(user)) { + throwAccountIsDisabledExceptionAndLog(user.getName()); + } + + PrincipalCollection collection = createPrincipalCollection(user, groups); + + AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(collection, token.getPassword()); + sessionStore.store(token); + return authenticationInfo; + } + + private PrincipalCollection createPrincipalCollection(User user, Set groups) { SimplePrincipalCollection collection = new SimplePrincipalCollection(); - - /* - * the first (primary) principal should be a unique identifier - */ collection.add(user.getId(), ScmRealm.NAME); collection.add(user, ScmRealm.NAME); collection.add(new GroupNames(groups), ScmRealm.NAME); - - return new SimpleAuthenticationInfo(collection, token.getPassword()); + return collection; } + private boolean isUserIsDisabled(User user) { + return !user.isActive(); + } - private Set authenticate(HttpServletRequest request, String password, AuthenticationResult ar) { - Set groupSet = null; - User user = ar.getUser(); - - try - { - groupSet = createGroupSet(ar); - - // check for admin user - checkForAuthenticatedAdmin(user, groupSet); - - // store user - User dbUser = userDAO.get(user.getName()); - - if (dbUser != null) - { - checkDBForAdmin(user, dbUser); - checkDBForActive(user, dbUser); - } - - // create new user - else if (user.isValid()) - { - user.setCreationDate(System.currentTimeMillis()); - - // TODO find a better way - UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_CREATE); - userDAO.add(user); - UserEventHack.fireEvent(userManager, user, HandlerEvent.CREATE); - } - else if (logger.isErrorEnabled()) - { - logger.error("could not create user {}, beacause it is not valid", - user.getName()); - } - - if (user.isActive()) - { - - if (logger.isDebugEnabled()) - { - logGroups(user, groupSet); - } - - // store encrypted credentials in session - String credentials = user.getName(); - - if (Util.isNotEmpty(password)) - { - credentials = credentials.concat(":").concat(password); - } - - credentials = CipherUtil.getInstance().encode(credentials); - request.getSession(true).setAttribute(SCM_CREDENTIALS, credentials); - } - else - { - - String msg = "user ".concat(user.getName()).concat(" is deactivated"); - - if (logger.isWarnEnabled()) - { - logger.warn(msg); - } - - throw new DisabledAccountException(msg); - - } - } - catch (IOException | UserException ex) - { - logger.error("authentication failed", ex); - - throw new AuthenticationException("authentication failed", ex); - } - - return groupSet; + private void throwAccountIsDisabledExceptionAndLog(String username) { + String msg = "user ".concat(username).concat(" is deactivated"); + LOG.warn(msg); + throw new DisabledAccountException(msg); } - - private void checkDBForActive(User user, User dbUser) - { - // user is deactivated by database - if (!dbUser.isActive()) - { - if (logger.isDebugEnabled()) - { - logger.debug("user {} is marked as deactivated by local database", - user.getName()); - } - - user.setActive(false); - } - } - - private void checkDBForAdmin(User user, User dbUser) throws UserException, IOException - { - - // if database user is an admin, set admin for the current user - if (dbUser.isAdmin()) - { - if (logger.isDebugEnabled()) - { - logger.debug("user {} of type {} is marked as admin by local database", - user.getName(), user.getType()); - } - - user.setAdmin(true); - } - - // modify existing user, copy properties except password and admin - if (user.copyProperties(dbUser, false)) - { - user.setLastModified(System.currentTimeMillis()); - UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_MODIFY); - userDAO.modify(user); - UserEventHack.fireEvent(userManager, user, HandlerEvent.MODIFY); - } - } - - private void checkForAuthenticatedAdmin(User user, Set groupSet) - { - if (!user.isAdmin()) - { - user.setAdmin(isAdmin(user, groupSet)); - - if (logger.isDebugEnabled() && user.isAdmin()) - { - logger.debug("user {} is marked as admin by configuration", - user.getName()); - } - } - else if (logger.isDebugEnabled()) - { - logger.debug("authenticator {} marked user {} as admin", user.getType(), - user.getName()); - } - } - - private Set createGroupSet(AuthenticationResult ar) - { - Set groupSet = Sets.newHashSet(); - - // add group for all authenticated users - groupSet.add(GroupNames.AUTHENTICATED); - - // load external groups - Collection extGroups = ar.getGroups(); - - if (extGroups != null) - { - groupSet.addAll(extGroups); - } - - // load internal groups - loadGroups(ar.getUser(), groupSet); - - return groupSet; - } - - private void loadGroups(User user, Set groupSet) - { - Collection groupCollection = groupManager.getGroupsForMember(user.getName()); - - if (groupCollection != null) - { - for (Group group : groupCollection) - { - groupSet.add(group.getName()); - } - } - } - - private void logGroups(User user, Set groups) - { - StringBuilder msg = new StringBuilder("user "); - - msg.append(user.getName()); - - if (Util.isNotEmpty(groups)) - { - msg.append(" is member of "); - - Joiner.on(", ").appendTo(msg, groups); - } - else - { - msg.append(" is not a member of a group"); - } - - logger.debug(msg.toString()); - } - - private boolean isAdmin(User user, Collection groups) - { - boolean result = false; - Set adminUsers = configuration.getAdminUsers(); - - if (adminUsers != null) - { - result = adminUsers.contains(user.getName()); - } - - if (!result) - { - Set adminGroups = configuration.getAdminGroups(); - - if (adminGroups != null) - { - result = Util.containsOne(adminGroups, groups); - } - } - - return result; - } - - } diff --git a/scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java b/scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java index 616351107e..3f1fa31b44 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java @@ -30,17 +30,33 @@ */ package sonia.scm.security; +import com.google.common.base.Joiner; +import com.google.common.collect.Sets; import com.google.inject.Inject; +import java.util.Collection; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.group.Group; import sonia.scm.group.GroupManager; +import sonia.scm.group.GroupNames; +import sonia.scm.user.User; +import sonia.scm.util.Util; import sonia.scm.web.security.AuthenticationResult; /** - * + * Collects groups from {@link GroupManager} after authentication. + * * @author Sebastian Sdorra + * @since 1.52 */ public class GroupCollector { + /** + * the logger for GroupCollector + */ + private static final Logger LOG = LoggerFactory.getLogger(GroupCollector.class); + private final GroupManager groupManager; @Inject @@ -48,8 +64,60 @@ public class GroupCollector { this.groupManager = groupManager; } - public Set collectGroups(AuthenticationResult result){ + /** + * Collect groups from {@link AuthenticationResult} and {@link GroupManager}. + * + * @param authenticationResult authentication result + * + * @return collected groups + */ + public Set collectGroups(AuthenticationResult authenticationResult) { + Set groups = Sets.newHashSet(); + + // add group for all authenticated users + groups.add(GroupNames.AUTHENTICATED); + + // load external groups + Collection groupsFromAuthenticator = authenticationResult.getGroups(); + + if (groupsFromAuthenticator != null) { + groups.addAll(groupsFromAuthenticator); + } + + User user = authenticationResult.getUser(); + loadGroupFromDatabase(user, groups); + + if (LOG.isDebugEnabled()) { + LOG.debug(createMembershipLogMessage(user, groups)); + } + return groups; + } + + private void loadGroupFromDatabase(User user, Set groupSet) { + Collection groupCollection = groupManager.getGroupsForMember(user.getName()); + + if (groupCollection != null) { + for (Group group : groupCollection) { + groupSet.add(group.getName()); + } + } + } + + private String createMembershipLogMessage(User user, Set groups) { + StringBuilder msg = new StringBuilder("user "); + + msg.append(user.getName()); + + if (Util.isNotEmpty(groups)) { + msg.append(" is member of "); + + Joiner.on(", ").appendTo(msg, groups); + } else { + msg.append(" is not a member of a group"); + } + + return msg.toString(); } } diff --git a/scm-webapp/src/main/java/sonia/scm/security/LocalDatabaseSynchronizer.java b/scm-webapp/src/main/java/sonia/scm/security/LocalDatabaseSynchronizer.java new file mode 100644 index 0000000000..af99ce5609 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/LocalDatabaseSynchronizer.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import com.google.inject.Inject; +import java.util.Set; +import org.apache.shiro.authc.DisabledAccountException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.HandlerEvent; +import sonia.scm.user.User; +import sonia.scm.user.UserDAO; +import sonia.scm.user.UserEventHack; +import sonia.scm.user.UserManager; + +/** + * Checks and synchronizes authenticated users with local database. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +public class LocalDatabaseSynchronizer { + + /** + * the logger for LocalDatabaseSynchronizer + */ + private static final Logger logger = LoggerFactory.getLogger(LocalDatabaseSynchronizer.class); + + private final AdminDetector adminSelector; + private final UserManager userManager; + private final UserDAO userDAO; + + @Inject + public LocalDatabaseSynchronizer(AdminDetector adminSelector, UserManager userManager, UserDAO userDAO) { + this.adminSelector = adminSelector; + this.userManager = userManager; + this.userDAO = userDAO; + } + + /** + * Check for disabled and administrator marks on authenticated users and synchronize the state with the local + * database. + * + * @param user authenticated user + * @param groups groups of authenticated user + */ + public void synchronize(User user, Set groups) { + adminSelector.checkForAuthenticatedAdmin(user, groups); + + User dbUser = userDAO.get(user.getId()); + if (dbUser != null) { + synchronizeWithLocalDatabase(user, dbUser); + } else if (user.isValid()) { + createUserInLocalDatabase(user); + } else { + logger.warn("could not create user {}, beacause it is not valid", user.getName()); + } + } + + private void synchronizeWithLocalDatabase(User user, User dbUser) { + synchronizeAdminFlag(user, dbUser); + synchronizeActiveFlag(user, dbUser); + modifyUserInLocalDatabase(user, dbUser); + } + + private void synchronizeAdminFlag(User user, User dbUser) { + // if database user is an admin, set admin for the current user + if (dbUser.isAdmin()) { + logger.debug("user {} of type {} is marked as admin by local database", user.getName(), user.getType()); + user.setAdmin(true); + } + } + + private void synchronizeActiveFlag(User user, User dbUser) { + // user is deactivated by database + if (!dbUser.isActive()) { + logger.debug("user {} is marked as deactivated by local database", user.getName()); + user.setActive(false); + } + } + + private void createUserInLocalDatabase(User user) { + user.setCreationDate(System.currentTimeMillis()); + UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_CREATE); + userDAO.add(user); + UserEventHack.fireEvent(userManager, user, HandlerEvent.CREATE); + } + + private void modifyUserInLocalDatabase(User user, User dbUser) { + // modify existing user, copy properties except password and admin + if (user.copyProperties(dbUser, false)) { + user.setLastModified(System.currentTimeMillis()); + UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_MODIFY); + userDAO.modify(user); + UserEventHack.fireEvent(userManager, user, HandlerEvent.MODIFY); + } + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/SessionStore.java b/scm-webapp/src/main/java/sonia/scm/security/SessionStore.java new file mode 100644 index 0000000000..88ae3c0912 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/SessionStore.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import javax.servlet.http.HttpServletRequest; +import org.apache.shiro.authc.UsernamePasswordToken; + +/** + * + * @author Sebastian Sdorra + */ +public class SessionStore { + + private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; + + private final Provider requestProvider; + + @Inject + public SessionStore(Provider requestProvider) { + this.requestProvider = requestProvider; + } + + public void store(UsernamePasswordToken token) { + // store encrypted credentials in session + String credentials = token.getUsername(); + + char[] password = token.getPassword(); + if (password != null && password.length > 0) { + credentials = credentials.concat(":").concat(new String(password)); + } + + credentials = CipherUtil.getInstance().encode(credentials); + requestProvider.get().getSession(true).setAttribute(SCM_CREDENTIALS, credentials); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java index 692a9493c7..42ea65e125 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java @@ -479,12 +479,18 @@ public class ScmRealmTest CacheManager cacheManager = new MapCacheManager(); + AdminDetector adminSelector = new AdminDetector(new ScmConfiguration()); + LocalDatabaseSynchronizer synchronizer = new LocalDatabaseSynchronizer( + adminSelector, userManager, userDAO + ); + + GroupCollector groupCollector = new GroupCollector(groupManager); + SessionStore sessionStore = new SessionStore(requestProvider); + AuthenticationInfoCollector authcCollector = new AuthenticationInfoCollector( - new ScmConfiguration(), - userManager, - groupManager, - userDAO, - requestProvider + synchronizer, + groupCollector, + sessionStore ); AuthorizationCollector authzCollector = new AuthorizationCollector( From acc3ff791b5660c06f25e739b5d159104d77e96b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 26 Feb 2017 13:19:35 +0100 Subject: [PATCH 04/56] #781 process all kinds authorization relevant event and produce AuthorizationChangedEvent --- .../security/AuthorizationChangedEvent.java | 89 ++++++ .../main/java/sonia/scm/ScmServletModule.java | 12 +- .../security/AuthenticationInfoCollector.java | 4 +- .../AuthorizationChangedEventProducer.java | 271 ++++++++++++++++++ .../scm/security/AuthorizationCollector.java | 216 ++------------ .../java/sonia/scm/security/ScmRealm.java | 46 ++- .../DefaultRepositoryManagerPerfTest.java | 14 +- ...AuthorizationChangedEventProducerTest.java | 256 +++++++++++++++++ .../security/AuthorizationCollectorTest.java | 179 +----------- .../java/sonia/scm/security/ScmRealmTest.java | 8 +- 10 files changed, 718 insertions(+), 377 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/security/AuthorizationChangedEvent.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java diff --git a/scm-core/src/main/java/sonia/scm/security/AuthorizationChangedEvent.java b/scm-core/src/main/java/sonia/scm/security/AuthorizationChangedEvent.java new file mode 100644 index 0000000000..0ef929e1fa --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/AuthorizationChangedEvent.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import sonia.scm.event.Event; + +/** + * This type of event is fired whenever a authorization relevant data changes. This event + * is especially useful for cache invalidation. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +@Event +public final class AuthorizationChangedEvent { + + private final String nameOfAffectedUser; + + private AuthorizationChangedEvent(String nameOfAffectedUser) { + this.nameOfAffectedUser = nameOfAffectedUser; + } + + /** + * Returns {@code true} if every user is affected by this data change. + * + * @return {@code true} if every user is affected + */ + public boolean isEveryUserAffected(){ + return nameOfAffectedUser != null; + } + + /** + * Returns the name of the user which is affected by this event. + * + * @return name of affected user + */ + public String getNameOfAffectedUser(){ + return nameOfAffectedUser; + } + + /** + * Creates a new event which affects every user. + * + * @return new event for every user + */ + public static AuthorizationChangedEvent createForEveryUser() { + return new AuthorizationChangedEvent(null); + } + + /** + * Create a new event which affect a single user. + * + * @param nameOfAffectedUser name of affected user + * + * @return new event for a single user + */ + public static AuthorizationChangedEvent createForUser(String nameOfAffectedUser) { + return new AuthorizationChangedEvent(nameOfAffectedUser); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 813c296558..1b8e0c7803 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -166,6 +166,7 @@ import sonia.scm.net.ahc.JsonContentTransformer; import sonia.scm.net.ahc.XmlContentTransformer; import sonia.scm.schedule.QuartzScheduler; import sonia.scm.schedule.Scheduler; +import sonia.scm.security.AuthorizationChangedEventProducer; import sonia.scm.security.XsrfProtectionFilter; import sonia.scm.web.UserAgentParser; @@ -300,6 +301,7 @@ public class ScmServletModule extends ServletModule pluginLoader.processExtensions(binder()); // bind security stuff + bind(AuthorizationChangedEventProducer.class); bind(PermissionResolver.class, RepositoryPermissionResolver.class); bind(AuthenticationManager.class, ChainAuthenticatonManager.class); bind(SecurityContext.class).to(BasicSecurityContext.class); @@ -310,6 +312,7 @@ public class ScmServletModule extends ServletModule // bind cache bind(CacheManager.class, GuavaCacheManager.class); + bind(org.apache.shiro.cache.CacheManager.class, GuavaCacheManager.class); // bind dao bind(GroupDAO.class, XmlGroupDAO.class); @@ -386,8 +389,7 @@ public class ScmServletModule extends ServletModule filter(PATTERN_ALL).through(BaseUrlFilter.class); filter(PATTERN_ALL).through(AutoLoginFilter.class); filterRegex(RESOURCE_REGEX).through(GZipFilter.class); - filter(PATTERN_RESTAPI, - PATTERN_DEBUG).through(ApiBasicAuthenticationFilter.class); + filter(PATTERN_RESTAPI, PATTERN_DEBUG).through(ApiBasicAuthenticationFilter.class); filter(PATTERN_RESTAPI, PATTERN_DEBUG).through(SecurityFilter.class); filter(PATTERN_CONFIG, PATTERN_ADMIN).through(AdminSecurityFilter.class); @@ -434,11 +436,7 @@ public class ScmServletModule extends ServletModule UriExtensionsConfig.class.getName()); String restPath = getRestPackages(); - - if (logger.isInfoEnabled()) - { - logger.info("configure jersey with package path: {}", restPath); - } + logger.info("configure jersey with package path: {}", restPath); params.put(PackagesResourceConfig.PROPERTY_PACKAGES, restPath); serve(PATTERN_RESTAPI).with(GuiceContainer.class, params); diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java b/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java index 8eb4de4c48..a82352b624 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java @@ -93,7 +93,7 @@ public class AuthenticationInfoCollector { synchronizer.synchronize(user, groups); - if (isUserIsDisabled(user)) { + if (isUserDisabled(user)) { throwAccountIsDisabledExceptionAndLog(user.getName()); } @@ -112,7 +112,7 @@ public class AuthenticationInfoCollector { return collection; } - private boolean isUserIsDisabled(User user) { + private boolean isUserDisabled(User user) { return !user.isActive(); } diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java new file mode 100644 index 0000000000..ab6f85c0e4 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationChangedEventProducer.java @@ -0,0 +1,271 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.eventbus.Subscribe; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.EagerSingleton; +import sonia.scm.ModificationHandlerEvent; +import sonia.scm.event.HandlerEventBase; +import sonia.scm.event.ScmEventBus; +import sonia.scm.group.Group; +import sonia.scm.group.GroupEvent; +import sonia.scm.group.GroupModificationEvent; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryEvent; +import sonia.scm.repository.RepositoryModificationEvent; +import sonia.scm.user.User; +import sonia.scm.user.UserEvent; +import sonia.scm.user.UserModificationEvent; + +/** + * Receives all kinds of events, which affects authorization relevant data and fires an + * {@link AuthorizationChangedEvent} if authorization data has changed. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +@EagerSingleton +public class AuthorizationChangedEventProducer { + + /** + * the logger for AuthorizationChangedEventProducer + */ + private static final Logger logger = LoggerFactory.getLogger(AuthorizationChangedEventProducer.class); + + /** + * Constructs a new instance. + */ + public AuthorizationChangedEventProducer() { + } + + /** + * Invalidates the cache of a user which was modified. The cache entries for the user will be invalidated for the + * following reasons: + *
    + *
  • Admin or Active flag was modified.
  • + *
  • New user created, for the case of old cache values
  • + *
  • User deleted
  • + *
+ * + * @param event user event + */ + @Subscribe + public void onEvent(UserEvent event) { + if (event.getEventType().isPost()) { + if (isModificationEvent(event)) { + handleUserModificationEvent((UserModificationEvent) event); + } else { + handleUserEvent(event); + } + } + } + + private boolean isModificationEvent(HandlerEventBase event) { + return event instanceof ModificationHandlerEvent; + } + + private void handleUserEvent(UserEvent event) { + String username = event.getItem().getName(); + logger.debug( + "fire authorization changed event for user {}, because of user {} event", username, event.getEventType() + ); + fireEventForUser(username); + } + + private void handleUserModificationEvent(UserModificationEvent event) { + String username = event.getItem().getId(); + User beforeModification = event.getItemBeforeModification(); + if (isAuthorizationDataModified(event.getItem(), beforeModification)) { + logger.debug( + "fire authorization changed event for user {}, because of a authorization relevant field has changed", + username + ); + fireEventForUser(username); + } else { + logger.debug( + "authorization changed event for user {} is not fired, because no authorization relevant field has changed", + username + ); + } + } + + private boolean isAuthorizationDataModified(User user, User beforeModification) { + return user.isAdmin() != beforeModification.isAdmin() || user.isActive() != beforeModification.isActive(); + } + + private void fireEventForUser(String username) { + sendEvent(AuthorizationChangedEvent.createForUser(username)); + } + + /** + * Invalidates the whole cache, if a repository has changed. The cache get cleared for one of the following reasons: + *
    + *
  • New repository created
  • + *
  • Repository was removed
  • + *
  • Archived, Public readable or permission field of the repository was modified
  • + *
+ * + * @param event repository event + */ + @Subscribe + public void onEvent(RepositoryEvent event) { + if (event.getEventType().isPost()) { + if (isModificationEvent(event)) { + handleRepositoryModificationEvent((RepositoryModificationEvent) event); + } else { + handleRepositoryEvent(event); + } + } + } + + private void handleRepositoryModificationEvent(RepositoryModificationEvent event) { + Repository repository = event.getItem(); + if (isAuthorizationDataModified(repository, event.getItemBeforeModification())) { + logger.debug( + "fire authorization changed event, because a relevant field of repository {} has changed", repository.getName() + ); + fireEventForEveryUser(); + } else { + logger.debug( + "authorization changed event is not fired, because non relevant field of repository {} has changed", + repository.getName() + ); + } + } + + private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) { + return repository.isArchived() != beforeModification.isArchived() + || repository.isPublicReadable() != beforeModification.isPublicReadable() + || ! repository.getPermissions().equals(beforeModification.getPermissions()); + } + + private void fireEventForEveryUser() { + sendEvent(AuthorizationChangedEvent.createForEveryUser()); + } + + private void handleRepositoryEvent(RepositoryEvent event){ + logger.debug( + "fire authorization changed event, because of received {} event for repository {}", + event.getEventType(), event.getItem().getName() + ); + fireEventForEveryUser(); + } + + /** + * Invalidates the whole cache if a group permission has changed and invalidates the cached entries of a user, if a + * user permission has changed. + * + * @param event permission event + */ + @Subscribe + public void onEvent(StoredAssignedPermissionEvent event) { + if (event.getEventType().isPost()) { + StoredAssignedPermission permission = event.getPermission(); + if (permission.isGroupPermission()) { + handleGroupPermissionChange(permission); + } else { + handleUserPermissionChange(permission); + } + } + } + + private void handleGroupPermissionChange(StoredAssignedPermission permission) { + logger.debug( + "fire authorization changed event, because global group permission {} has changed", + permission.getId() + ); + fireEventForEveryUser(); + } + + private void handleUserPermissionChange(StoredAssignedPermission permission) { + logger.debug( + "fire authorization changed event for user {}, because permission {} has changed", + permission.getName(), permission.getId() + ); + fireEventForUser(permission.getName()); + } + + /** + * Invalidates the whole cache, if a group has changed. The cache get cleared for one of the following reasons: + *
    + *
  • New group created
  • + *
  • Group was removed
  • + *
  • Group members was modified
  • + *
+ * + * @param event group event + */ + @Subscribe + public void onEvent(GroupEvent event) { + if (event.getEventType().isPost()) { + if (isModificationEvent(event)) { + handleGroupModificationEvent((GroupModificationEvent) event); + } else { + handleGroupEvent(event); + } + } + } + + private void handleGroupModificationEvent(GroupModificationEvent event) { + Group group = event.getItem(); + if (isAuthorizationDataModified(group, event.getItemBeforeModification())) { + logger.debug("fire authorization changed event, because group {} has changed", group.getId()); + fireEventForEveryUser(); + } else { + logger.debug( + "authorization changed event is not fired, because non relevant field of group {} has changed", + group.getId() + ); + } + } + + private boolean isAuthorizationDataModified(Group group, Group beforeModification) { + return !group.getMembers().equals(beforeModification.getMembers()); + } + + private void handleGroupEvent(GroupEvent event){ + logger.debug( + "fire authorization changed event, because of received group event {} for group {}", + event.getEventType(), + event.getItem().getId() + ); + fireEventForEveryUser(); + } + + @VisibleForTesting + protected void sendEvent(AuthorizationChangedEvent event) { + ScmEventBus.getInstance().post(event); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationCollector.java index cbc22ee553..af9389ea8f 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthorizationCollector.java @@ -57,14 +57,11 @@ import org.slf4j.LoggerFactory; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; -import sonia.scm.group.GroupEvent; import sonia.scm.group.GroupNames; import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; -import sonia.scm.repository.RepositoryEvent; import sonia.scm.user.User; -import sonia.scm.user.UserEvent; import sonia.scm.util.Util; //~--- JDK imports ------------------------------------------------------------ @@ -72,10 +69,6 @@ import sonia.scm.util.Util; import java.util.List; import java.util.Set; import sonia.scm.Filter; -import sonia.scm.group.Group; -import sonia.scm.group.GroupModificationEvent; -import sonia.scm.repository.RepositoryModificationEvent; -import sonia.scm.user.UserModificationEvent; /** * @@ -98,7 +91,7 @@ public class AuthorizationCollector LoggerFactory.getLogger(AuthorizationCollector.class); //~--- constructors --------------------------------------------------------- - + /** * Constructs ... * @@ -114,8 +107,7 @@ public class AuthorizationCollector RepositoryDAO repositoryDAO, SecuritySystem securitySystem, PermissionResolver resolver) { - this.cache = cacheManager.getCache(CacheKey.class, AuthorizationInfo.class, - CACHE_NAME); + this.cache = cacheManager.getCache(CacheKey.class, AuthorizationInfo.class, CACHE_NAME); this.repositoryDAO = repositoryDAO; this.securitySystem = securitySystem; this.resolver = resolver; @@ -146,189 +138,9 @@ public class AuthorizationCollector return authorizationInfo; } - /** - * Invalidates the cache of a user which was modified. The cache entries for the user will be invalidated for the - * following reasons: - *
    - *
  • Admin or Active flag was modified.
  • - *
  • New user created, for the case of old cache values
  • - *
  • User deleted
  • - *
- * - * @param event user event - */ - @Subscribe - public void onEvent(UserEvent event) - { - if (event.getEventType().isPost()) - { - User user = event.getItem(); - String username = user.getId(); - if (event instanceof UserModificationEvent) - { - User beforeModification = ((UserModificationEvent) event).getItemBeforeModification(); - if (shouldCacheBeCleared(user, beforeModification)) - { - logger.debug("invalidate cache of user {}, because of a permission relevant field has changed", username); - invalidateUserCache(username); - } - else - { - logger.debug("cache of user {} is not invalidated, because no permission relevant field has changed", username); - } - } - else - { - logger.debug("invalidate cache of user {}, because of user {} event", username, event.getEventType()); - invalidateUserCache(username); - } - } - } - - private boolean shouldCacheBeCleared(User user, User beforeModification) - { - return user.isAdmin() != beforeModification.isAdmin() || user.isActive() != beforeModification.isActive(); - } - - private void invalidateUserCache(final String username) - { - cache.removeAll(new Filter() - { - @Override - public boolean accept(CacheKey item) - { - return username.equalsIgnoreCase(item.username); - } - }); - } - - /** - * Invalidates the whole cache, if a repository has changed. The cache get cleared for one of the following reasons: - *
    - *
  • New repository created
  • - *
  • Repository was removed
  • - *
  • Archived, Public readable or permission field of the repository was modified
  • - *
- * - * @param event repository event - */ - @Subscribe - public void onEvent(RepositoryEvent event) - { - if (event.getEventType().isPost()) - { - Repository repository = event.getItem(); - - if (event instanceof RepositoryModificationEvent) - { - Repository beforeModification = ((RepositoryModificationEvent) event).getItemBeforeModification(); - if (shouldCacheBeCleared(repository, beforeModification)) - { - logger.debug("clear cache, because a relevant field of repository {} has changed", repository.getName()); - cache.clear(); - } - else - { - logger.debug( - "cache is not invalidated, because non relevant field of repository {} has changed", - repository.getName() - ); - } - } - else - { - logger.debug("clear cache, received {} event of repository {}", event.getEventType(), repository.getName()); - cache.clear(); - } - } - } - - private boolean shouldCacheBeCleared(Repository repository, Repository beforeModification) - { - return repository.isArchived() != beforeModification.isArchived() - || repository.isPublicReadable() != beforeModification.isPublicReadable() - || ! repository.getPermissions().equals(beforeModification.getPermissions()); - } - - /** - * Invalidates the whole cache if a group permission has changed and invalidates the cached entries of a user, if a - * user permission has changed. - * - * - * @param event permission event - */ - @Subscribe - public void onEvent(StoredAssignedPermissionEvent event) - { - if (event.getEventType().isPost()) - { - StoredAssignedPermission permission = event.getPermission(); - if (permission.isGroupPermission()) - { - logger.debug("clear cache, because global group permission {} has changed", permission.getId()); - cache.clear(); - } - else - { - logger.debug( - "clear cache of user {}, because permission {} has changed", - permission.getName(), event.getPermission().getId() - ); - invalidateUserCache(permission.getName()); - } - } - } - - /** - * Invalidates the whole cache, if a group has changed. The cache get cleared for one of the following reasons: - *
    - *
  • New group created
  • - *
  • Group was removed
  • - *
  • Group members was modified
  • - *
- * - * @param event group event - */ - @Subscribe - public void onEvent(GroupEvent event) - { - if (event.getEventType().isPost()) - { - Group group = event.getItem(); - if (event instanceof GroupModificationEvent) - { - Group beforeModification = ((GroupModificationEvent) event).getItemBeforeModification(); - if (shouldCacheBeCleared(group, beforeModification)) - { - logger.debug("clear cache, because group {} has changed", group.getId()); - cache.clear(); - } - else - { - logger.debug( - "cache is not invalidated, because non relevant field of group {} has changed", - group.getId() - ); - } - } - else - { - logger.debug("clear cache, received group event {} for group {}", event.getEventType(), group.getId()); - cache.clear(); - } - } - } - - private boolean shouldCacheBeCleared(Group group, Group beforeModification) - { - return !group.getMembers().equals(beforeModification.getMembers()); - } - /** * Method description * - * - * * @param principals * * @return @@ -567,6 +379,30 @@ public class AuthorizationCollector || ((!perm.isGroupPermission()) && user.getName().equals(perm.getName())); //J+ } + + @Subscribe + public void invalidateCache(AuthorizationChangedEvent event) { + if (event.isEveryUserAffected()) { + invalidateUserCache(event.getNameOfAffectedUser()); + } else { + invalidateCache(); + } + } + + private void invalidateUserCache(final String username) { + logger.info("invalidate cache for user {}, because of a received authorization event", username); + cache.removeAll(new Filter() { + @Override + public boolean accept(CacheKey item) { + return username.equalsIgnoreCase(item.username); + } + }); + } + + private void invalidateCache() { + logger.info("invalidate cache, because of a received authorization event"); + cache.clear(); + } //~--- inner classes -------------------------------------------------------- diff --git a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java index b49c9a6f3c..0cb77372a0 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java @@ -35,6 +35,7 @@ package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.eventbus.Subscribe; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -46,9 +47,11 @@ import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.pam.UnsupportedTokenException; import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.cache.CacheManager; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sonia.scm.web.security.AuthenticationResult; import sonia.scm.web.security.AuthenticationState; @@ -63,6 +66,11 @@ import sonia.scm.web.security.AuthenticationState; @Singleton public class ScmRealm extends AuthorizingRealm { + /** + * the logger for ScmRealm + */ + private static final Logger logger = LoggerFactory.getLogger(ScmRealm.class); + public static final String NAME = "scm"; @@ -74,32 +82,35 @@ public class ScmRealm extends AuthorizingRealm { /** * Constructs a new scm realm. * + * @param cacheManager cache manager * @param authenticator authenticator facade * @param loginAttemptHandler login attempt handler * @param authcCollector authentication info collector * @param authzCollector authorization collector */ @Inject - public ScmRealm(AuthenticatorFacade authenticator, LoginAttemptHandler loginAttemptHandler, + public ScmRealm(CacheManager cacheManager, + AuthenticatorFacade authenticator, LoginAttemptHandler loginAttemptHandler, AuthenticationInfoCollector authcCollector, AuthorizationCollector authzCollector) { + super(cacheManager); + this.authenticator = authenticator; this.loginAttemptHandler = loginAttemptHandler; this.authcCollector = authcCollector; this.authzCollector = authzCollector; - // set token class setAuthenticationTokenClass(UsernamePasswordToken.class); - // use own custom caching - setCachingEnabled(false); - setAuthenticationCachingEnabled(false); - setAuthorizationCachingEnabled(false); - // set components setPermissionResolver(new RepositoryPermissionResolver()); } + @Override + protected Object getAuthorizationCacheKey(PrincipalCollection principals) { + return principals.getPrimaryPrincipal(); + } + @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException { UsernamePasswordToken token = castToken(authToken); @@ -138,4 +149,23 @@ public class ScmRealm extends AuthorizingRealm { protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){ return authzCollector.collect(principals); } + + @Subscribe + public void invalidateCache(AuthorizationChangedEvent event) { + if (event.isEveryUserAffected()) { + invalidateUserCache(event.getNameOfAffectedUser()); + } else { + invalidateCache(); + } + } + + private void invalidateUserCache(final String username) { + logger.info("invalidate cache for user {}, because of a received authorization event", username); + getAuthorizationCache().remove(username); + } + + private void invalidateCache() { + logger.info("invalidate cache, because of a received authorization event"); + getAuthorizationCache().clear(); + } } diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java index 9fb78f5879..e00b2ccd6d 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java @@ -72,8 +72,11 @@ import sonia.scm.security.SecuritySystem; import sonia.scm.user.UserTestData; /** - * + * Performance test for {@link RepositoryManager#getAll()}. + * + * @see Issue 781 * @author Sebastian Sdorra + * @since 1.52 */ @RunWith(MockitoJUnitRunner.class) public class DefaultRepositoryManagerPerfTest { @@ -105,6 +108,9 @@ public class DefaultRepositoryManagerPerfTest { private AuthorizationCollector authzCollector; + /** + * Setup object under test. + */ @Before public void setUpObjectUnderTest(){ when(repositoryHandler.getType()).thenReturn(new Type(REPOSITORY_TYPE, REPOSITORY_TYPE)); @@ -132,11 +138,17 @@ public class DefaultRepositoryManagerPerfTest { ThreadContext.bind(securityManager); } + /** + * Tear down test objects. + */ @After public void tearDown(){ ThreadContext.unbindSecurityManager(); } + /** + * Start performance test and ensure that the timeout is not reached. + */ @Test(timeout = 6000l) public void perfTestGetAll(){ SecurityUtils.getSubject().login(new UsernamePasswordToken("trillian", "secret")); diff --git a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java new file mode 100644 index 0000000000..aba1844bb1 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationChangedEventProducerTest.java @@ -0,0 +1,256 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.security; + +import com.google.common.collect.Lists; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Before; +import sonia.scm.HandlerEvent; +import sonia.scm.group.Group; +import sonia.scm.group.GroupEvent; +import sonia.scm.group.GroupModificationEvent; +import sonia.scm.repository.PermissionType; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryEvent; +import sonia.scm.repository.RepositoryModificationEvent; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.user.User; +import sonia.scm.user.UserEvent; +import sonia.scm.user.UserModificationEvent; +import sonia.scm.user.UserTestData; + +/** + * Unit tests for {@link AuthorizationChangedEventProducer}. + * + * @author Sebastian Sdorra + */ +public class AuthorizationChangedEventProducerTest { + + private StoringAuthorizationChangedEventProducer producer; + + @Before + public void setUpProducer() { + producer = new StoringAuthorizationChangedEventProducer(); + } + + /** + * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.user.UserEvent)}. + */ + @Test + public void testOnUserEvent() + { + User user = UserTestData.createDent(); + producer.onEvent(new UserEvent(user, HandlerEvent.BEFORE_CREATE)); + assertEventIsNotFired(); + + producer.onEvent(new UserEvent(user, HandlerEvent.CREATE)); + assertUserEventIsFired("dent"); + } + + private void assertEventIsNotFired(){ + assertNull(producer.event); + } + + private void assertUserEventIsFired(String username){ + assertNotNull(producer.event); + assertTrue(producer.event.isEveryUserAffected()); + assertEquals(username, producer.event.getNameOfAffectedUser()); + } + + /** + * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.user.UserEvent)} with modified user. + */ + @Test + public void testOnUserModificationEvent() + { + User user = UserTestData.createDent(); + User userModified = UserTestData.createDent(); + userModified.setDisplayName("Super Dent"); + + producer.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.BEFORE_CREATE)); + assertEventIsNotFired(); + + producer.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.CREATE)); + assertEventIsNotFired(); + + userModified.setAdmin(true); + + producer.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.BEFORE_CREATE)); + assertEventIsNotFired(); + + producer.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.CREATE)); + assertUserEventIsFired("dent"); + } + + /** + * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)}. + */ + @Test + public void testOnGroupEvent() + { + Group group = new Group("xml", "base"); + producer.onEvent(new GroupEvent(group, HandlerEvent.BEFORE_CREATE)); + assertEventIsNotFired(); + + producer.onEvent(new GroupEvent(group, HandlerEvent.CREATE)); + assertGlobalEventIsFired(); + } + + private void assertGlobalEventIsFired(){ + assertNotNull(producer.event); + assertFalse(producer.event.isEveryUserAffected()); + } + + /** + * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)} with modified groups. + */ + @Test + public void testOnGroupModificationEvent() + { + Group group = new Group("xml", "base"); + Group modifiedGroup = new Group("xml", "base"); + producer.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.BEFORE_MODIFY)); + assertEventIsNotFired(); + + producer.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.MODIFY)); + assertEventIsNotFired(); + + modifiedGroup.add("test"); + producer.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.MODIFY)); + assertGlobalEventIsFired(); + } + + /** + * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.repository.RepositoryEvent)}. + */ + @Test + public void testOnRepositoryEvent() + { + Repository repository = RepositoryTestData.createHeartOfGold(); + producer.onEvent(new RepositoryEvent(repository, HandlerEvent.BEFORE_CREATE)); + assertEventIsNotFired(); + + producer.onEvent(new RepositoryEvent(repository, HandlerEvent.CREATE)); + assertGlobalEventIsFired(); + } + + /** + * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.repository.RepositoryEvent)} with modified + * repository. + */ + @Test + public void testOnRepositoryModificationEvent() + { + Repository repositoryModified = RepositoryTestData.createHeartOfGold(); + repositoryModified.setName("test123"); + repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test"))); + + Repository repository = RepositoryTestData.createHeartOfGold(); + repository.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test"))); + + producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.BEFORE_CREATE)); + assertEventIsNotFired(); + + producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); + assertEventIsNotFired(); + + repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test"))); + producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); + assertEventIsNotFired(); + + repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test123"))); + producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); + assertGlobalEventIsFired(); + + resetStoredEvent(); + + repositoryModified.setPermissions( + Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.READ, true)) + ); + producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); + assertGlobalEventIsFired(); + + resetStoredEvent(); + + repositoryModified.setPermissions( + Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.WRITE)) + ); + producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); + assertGlobalEventIsFired(); + } + + private void resetStoredEvent(){ + producer.event = null; + } + + /** + * Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.security.StoredAssignedPermissionEvent)}. + */ + @Test + public void testOnStoredAssignedPermissionEvent() + { + StoredAssignedPermission groupPermission = new StoredAssignedPermission( + "123", new AssignedPermission("_authenticated", true, "repository:read:*") + ); + producer.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.BEFORE_CREATE, groupPermission)); + assertEventIsNotFired(); + + producer.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.CREATE, groupPermission)); + assertGlobalEventIsFired(); + + resetStoredEvent(); + + StoredAssignedPermission userPermission = new StoredAssignedPermission( + "123", new AssignedPermission("trillian", false, "repository:read:*") + ); + producer.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.BEFORE_CREATE, userPermission)); + assertEventIsNotFired(); + + resetStoredEvent(); + + producer.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.CREATE, userPermission)); + assertUserEventIsFired("trillian"); + } + + private static class StoringAuthorizationChangedEventProducer extends AuthorizationChangedEventProducer { + + private AuthorizationChangedEvent event; + + @Override + protected void sendEvent(AuthorizationChangedEvent event) { + this.event = event; + } + + } + +} \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationCollectorTest.java index 269f39f833..f6b69bd848 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/AuthorizationCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/AuthorizationCollectorTest.java @@ -54,22 +54,14 @@ import static org.junit.Assert.*; import org.junit.Rule; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.Filter; -import sonia.scm.HandlerEvent; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; -import sonia.scm.group.Group; -import sonia.scm.group.GroupEvent; -import sonia.scm.group.GroupModificationEvent; import sonia.scm.group.GroupNames; import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; -import sonia.scm.repository.RepositoryEvent; -import sonia.scm.repository.RepositoryModificationEvent; import sonia.scm.repository.RepositoryTestData; import sonia.scm.user.User; -import sonia.scm.user.UserEvent; -import sonia.scm.user.UserModificationEvent; import sonia.scm.user.UserTestData; /** @@ -85,7 +77,7 @@ public class AuthorizationCollectorTest { @Mock private CacheManager cacheManager; - + @Mock private RepositoryDAO repositoryDAO; @@ -111,160 +103,6 @@ public class AuthorizationCollectorTest { collector = new AuthorizationCollector(cacheManager, repositoryDAO, securitySystem, resolver); } - /** - * Tests {@link AuthorizationCollector#onEvent(sonia.scm.user.UserEvent)}. - */ - @Test - public void testOnUserEvent() - { - User user = UserTestData.createDent(); - collector.onEvent(new UserEvent(user, HandlerEvent.BEFORE_CREATE)); - verify(cache, never()).clear(); - - collector.onEvent(new UserEvent(user, HandlerEvent.CREATE)); - verify(cache).removeAll(Mockito.any(Filter.class)); - } - - /** - * Tests {@link AuthorizationCollector#onEvent(sonia.scm.user.UserEvent)} with modified user. - */ - @Test - public void testOnUserModificationEvent() - { - User user = UserTestData.createDent(); - User userModified = UserTestData.createDent(); - userModified.setDisplayName("Super Dent"); - - collector.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.BEFORE_CREATE)); - verify(cache, never()).removeAll(Mockito.any(Filter.class)); - - collector.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.CREATE)); - verify(cache, never()).removeAll(Mockito.any(Filter.class)); - - userModified.setAdmin(true); - - collector.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.BEFORE_CREATE)); - verify(cache, never()).removeAll(Mockito.any(Filter.class)); - - collector.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.CREATE)); - verify(cache).removeAll(Mockito.any(Filter.class)); - } - - /** - * Tests {@link AuthorizationCollector#onEvent(sonia.scm.group.GroupEvent)}. - */ - @Test - public void testOnGroupEvent() - { - Group group = new Group("xml", "base"); - collector.onEvent(new GroupEvent(group, HandlerEvent.BEFORE_CREATE)); - verify(cache, never()).clear(); - - collector.onEvent(new GroupEvent(group, HandlerEvent.CREATE)); - verify(cache).clear(); - } - - /** - * Tests {@link AuthorizationCollector#onEvent(sonia.scm.group.GroupEvent)} with modified groups. - */ - @Test - public void testOnGroupModificationEvent() - { - Group group = new Group("xml", "base"); - Group modifiedGroup = new Group("xml", "base"); - collector.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.BEFORE_MODIFY)); - verify(cache, never()).clear(); - - collector.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.MODIFY)); - verify(cache, never()).clear(); - - modifiedGroup.add("test"); - collector.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.MODIFY)); - verify(cache).clear(); - } - - /** - * Tests {@link AuthorizationCollector#onEvent(sonia.scm.repository.RepositoryEvent)}. - */ - @Test - public void testOnRepositoryEvent() - { - Repository repository = RepositoryTestData.createHeartOfGold(); - collector.onEvent(new RepositoryEvent(repository, HandlerEvent.BEFORE_CREATE)); - verify(cache, never()).clear(); - - collector.onEvent(new RepositoryEvent(repository, HandlerEvent.CREATE)); - verify(cache).clear(); - } - - /** - * Tests {@link AuthorizationCollector#onEvent(sonia.scm.repository.RepositoryEvent)} with modified repository. - */ - @Test - public void testOnRepositoryModificationEvent() - { - Repository repositoryModified = RepositoryTestData.createHeartOfGold(); - repositoryModified.setName("test123"); - repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test"))); - - Repository repository = RepositoryTestData.createHeartOfGold(); - repository.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test"))); - - collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.BEFORE_CREATE)); - verify(cache, never()).clear(); - - collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); - verify(cache, never()).clear(); - - repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test"))); - collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); - verify(cache, never()).clear(); - - repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test123"))); - collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); - verify(cache).clear(); - - repositoryModified.setPermissions( - Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.READ, true)) - ); - collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); - verify(cache, times(2)).clear(); - - repositoryModified.setPermissions( - Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.WRITE)) - ); - collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE)); - verify(cache, times(3)).clear(); - } - - /** - * Tests {@link AuthorizationCollector#onEvent(sonia.scm.security.StoredAssignedPermissionEvent)}. - */ - @Test - public void testOnStoredAssignedPermissionEvent() - { - StoredAssignedPermission groupPermission = new StoredAssignedPermission( - "123", new AssignedPermission("_authenticated", true, "repository:read:*") - ); - collector.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.BEFORE_CREATE, groupPermission)); - verify(cache, never()).clear(); - - collector.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.CREATE, groupPermission)); - verify(cache).clear(); - - - StoredAssignedPermission userPermission = new StoredAssignedPermission( - "123", new AssignedPermission("trillian", false, "repository:read:*") - ); - collector.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.BEFORE_CREATE, userPermission)); - verify(cache, never()).removeAll(Mockito.any(Filter.class)); - verify(cache).clear(); - - collector.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.CREATE, userPermission)); - verify(cache).removeAll(Mockito.any(Filter.class)); - verify(cache).clear(); - } - /** * Tests {@link AuthorizationCollector#collect()} without user role. */ @@ -402,8 +240,7 @@ public class AuthorizationCollectorTest { assertThat(authInfo.getObjectPermissions(), containsInAnyOrder(wp1, wp2)); } - private void authenticate(User user, String group, String... groups) - { + private void authenticate(User user, String group, String... groups) { SimplePrincipalCollection spc = new SimplePrincipalCollection(); spc.add(user.getName(), "unit"); spc.add(user, "unit"); @@ -412,4 +249,16 @@ public class AuthorizationCollectorTest { shiro.setSubject(subject); } + /** + * Tests {@link AuthorizationCollector#invalidateCache(sonia.scm.security.AuthorizationChangedEvent)}. + */ + @Test + public void testInvalidateCache() { + collector.invalidateCache(AuthorizationChangedEvent.createForEveryUser()); + verify(cache).clear(); + + collector.invalidateCache(AuthorizationChangedEvent.createForUser("dent")); + verify(cache).removeAll(Mockito.any(Filter.class)); + } + } \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java index 42ea65e125..2bbbbdd5e5 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java @@ -54,8 +54,6 @@ import org.junit.Test; import org.mockito.Mockito; -import sonia.scm.cache.CacheManager; -import sonia.scm.cache.MapCacheManager; import sonia.scm.config.ScmConfiguration; import sonia.scm.group.Group; import sonia.scm.group.GroupManager; @@ -89,6 +87,7 @@ import java.util.concurrent.atomic.AtomicLong; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import sonia.scm.cache.GuavaCacheManager; /** * @@ -477,7 +476,7 @@ public class ScmRealmTest Collections.EMPTY_LIST ); - CacheManager cacheManager = new MapCacheManager(); + GuavaCacheManager cacheManager = new GuavaCacheManager(); AdminDetector adminSelector = new AdminDetector(new ScmConfiguration()); LocalDatabaseSynchronizer synchronizer = new LocalDatabaseSynchronizer( @@ -494,7 +493,7 @@ public class ScmRealmTest ); AuthorizationCollector authzCollector = new AuthorizationCollector( - cacheManager, + cacheManager, repositoryDAO, securitySystem, new RepositoryPermissionResolver() @@ -516,6 +515,7 @@ public class ScmRealmTest }; return new ScmRealm( + cacheManager, new AuthenticatorFacade(authManager, requestProvider, responseProvider), dummyLoginAttemptHandler, authcCollector, From 402d2cfdb7396326d4b8a3546be73607e7c7156b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 26 Feb 2017 14:28:00 +0100 Subject: [PATCH 05/56] #781 added missing unit tests for authentication related classes --- .../sonia/scm/security/AdminDetector.java | 2 +- .../security/LocalDatabaseSynchronizer.java | 1 - .../sonia/scm/security/AdminDetectorTest.java | 121 ++++++++++++++++++ .../AuthenticationInfoCollectorTest.java | 110 ++++++++++++++++ .../scm/security/GroupCollectorTest.java | 107 ++++++++++++++++ .../LocalDatabaseSynchronizerTest.java | 106 +++++++++++++++ 6 files changed, 445 insertions(+), 2 deletions(-) create mode 100644 scm-webapp/src/test/java/sonia/scm/security/AdminDetectorTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/AuthenticationInfoCollectorTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/GroupCollectorTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/LocalDatabaseSynchronizerTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java b/scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java index ddc43d8b0a..ec1e4bd711 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java @@ -85,8 +85,8 @@ public class AdminDetector { private boolean isAdminByConfiguration(User user, Collection groups) { boolean result = false; + Set adminUsers = configuration.getAdminUsers(); - if (adminUsers != null) { result = adminUsers.contains(user.getName()); } diff --git a/scm-webapp/src/main/java/sonia/scm/security/LocalDatabaseSynchronizer.java b/scm-webapp/src/main/java/sonia/scm/security/LocalDatabaseSynchronizer.java index af99ce5609..e37e31709b 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/LocalDatabaseSynchronizer.java +++ b/scm-webapp/src/main/java/sonia/scm/security/LocalDatabaseSynchronizer.java @@ -32,7 +32,6 @@ package sonia.scm.security; import com.google.inject.Inject; import java.util.Set; -import org.apache.shiro.authc.DisabledAccountException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.HandlerEvent; diff --git a/scm-webapp/src/test/java/sonia/scm/security/AdminDetectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/AdminDetectorTest.java new file mode 100644 index 0000000000..253375caff --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/AdminDetectorTest.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.security; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Before; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.user.User; +import sonia.scm.user.UserTestData; + +/** + * Unit tests for {@link AdminDetector}. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +public class AdminDetectorTest { + + private ScmConfiguration configuration; + private AdminDetector detector; + + @Before + public void setUpObjectUnderTest(){ + configuration = new ScmConfiguration(); + detector = new AdminDetector(configuration); + } + + /** + * Tests {@link AdminDetector#checkForAuthenticatedAdmin(User, Set)} with configured admin users. + */ + @Test + public void testCheckForAuthenticatedAdminWithConfiguredAdminUsers() { + configuration.setAdminUsers(ImmutableSet.of("slarti")); + + User slarti = UserTestData.createSlarti(); + slarti.setAdmin(false); + Set groups = ImmutableSet.of(); + + detector.checkForAuthenticatedAdmin(slarti, groups); + assertTrue(slarti.isAdmin()); + } + + /** + * Tests {@link AdminDetector#checkForAuthenticatedAdmin(User, Set)} with configured admin group. + */ + @Test + public void testCheckForAuthenticatedAdminWithConfiguredAdminGroup() { + configuration.setAdminGroups(ImmutableSet.of("heartOfGold")); + + User slarti = UserTestData.createSlarti(); + slarti.setAdmin(false); + Set groups = ImmutableSet.of("heartOfGold"); + + detector.checkForAuthenticatedAdmin(slarti, groups); + assertTrue(slarti.isAdmin()); + } + + /** + * Tests {@link AdminDetector#checkForAuthenticatedAdmin(User, Set)} with non matching configuration. + */ + @Test + public void testCheckForAuthenticatedAdminWithNonMatchinConfiguration() { + configuration.setAdminUsers(ImmutableSet.of("slarti")); + configuration.setAdminGroups(ImmutableSet.of("heartOfGold")); + + User trillian = UserTestData.createTrillian(); + trillian.setAdmin(false); + Set groups = ImmutableSet.of("puzzle42"); + + detector.checkForAuthenticatedAdmin(trillian, groups); + assertFalse(trillian.isAdmin()); + } + +/** + * Tests {@link AdminDetector#checkForAuthenticatedAdmin(User, Set)} with user which is already admin. + */ + @Test + public void testCheckForAuthenticatedAdminWithUserWhichIsAlreadyAdmin() { + configuration.setAdminUsers(ImmutableSet.of("slarti")); + configuration.setAdminGroups(ImmutableSet.of("heartOfGold")); + + User trillian = UserTestData.createTrillian(); + trillian.setAdmin(true); + Set groups = ImmutableSet.of("puzzle42"); + + detector.checkForAuthenticatedAdmin(trillian, groups); + assertTrue(trillian.isAdmin()); + } +} \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/security/AuthenticationInfoCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/AuthenticationInfoCollectorTest.java new file mode 100644 index 0000000000..31504777fd --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/AuthenticationInfoCollectorTest.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.security; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.DisabledAccountException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.junit.Test; +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.group.GroupNames; +import sonia.scm.user.User; +import sonia.scm.user.UserTestData; +import sonia.scm.web.security.AuthenticationResult; + +/** + * Unit tests for {@link AuthenticationInfoCollector}. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +@RunWith(MockitoJUnitRunner.class) +public class AuthenticationInfoCollectorTest { + + @Mock + private LocalDatabaseSynchronizer synchronizer; + + @Mock + private GroupCollector groupCollector; + + @Mock + private SessionStore sessionStore; + + @InjectMocks + private AuthenticationInfoCollector collector; + + /** + * Tests {@link AuthenticationInfoCollector#createAuthenticationInfo(UsernamePasswordToken, AuthenticationResult)}. + */ + @Test + public void testCreateAuthenticationInfo() { + User trillian = UserTestData.createTrillian(); + UsernamePasswordToken token = new UsernamePasswordToken(trillian.getId(), "secret"); + AuthenticationResult result = new AuthenticationResult(trillian); + Set groups = ImmutableSet.of("puzzle42", "heartOfGold"); + when(groupCollector.collectGroups(Mockito.any(AuthenticationResult.class))).thenReturn(groups); + + AuthenticationInfo authc = collector.createAuthenticationInfo(token, result); + verify(synchronizer).synchronize(trillian, groups); + verify(sessionStore).store(token); + + assertEquals(trillian.getId(), authc.getPrincipals().getPrimaryPrincipal()); + assertEquals(trillian, authc.getPrincipals().oneByType(User.class)); + assertThat(authc.getPrincipals().oneByType(GroupNames.class), contains(groups.toArray(new String[0]))); + } + + /** + * Tests {@link AuthenticationInfoCollector#createAuthenticationInfo(UsernamePasswordToken, AuthenticationResult)} + * with disabled user. + */ + @Test(expected = DisabledAccountException.class) + public void testCreateAuthenticationInfoWithDisabledUser() { + User trillian = UserTestData.createTrillian(); + trillian.setActive(false); + UsernamePasswordToken token = new UsernamePasswordToken(trillian.getId(), "secret"); + AuthenticationResult result = new AuthenticationResult(trillian); + Set groups = ImmutableSet.of("puzzle42", "heartOfGold"); + when(groupCollector.collectGroups(Mockito.any(AuthenticationResult.class))).thenReturn(groups); + + collector.createAuthenticationInfo(token, result); + } + +} \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/security/GroupCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/GroupCollectorTest.java new file mode 100644 index 0000000000..57f0362549 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/GroupCollectorTest.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.security; + +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import jdk.nashorn.internal.ir.annotations.Immutable; +import org.junit.Test; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.hamcrest.Matchers.*; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.user.UserTestData; +import sonia.scm.web.security.AuthenticationResult; + +/** + * Unit tests for {@link GroupCollector}. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +@RunWith(MockitoJUnitRunner.class) +public class GroupCollectorTest { + + @Mock + private GroupManager groupManager; + + @InjectMocks + private GroupCollector collector; + + /** + * Tests {@link GroupCollector#collectGroups(AuthenticationResult)} without groups from authenticator. + */ + @Test + public void testCollectGroupsWithoutAuthenticatorGroups() { + Set groups = collector.collectGroups(new AuthenticationResult(UserTestData.createSlarti())); + assertThat(groups, containsInAnyOrder("_authenticated")); + } + + /** + * Tests {@link GroupCollector#collectGroups(AuthenticationResult)} with groups from authenticator. + */ + @Test + public void testCollectGroupsWithGroupsFromAuthenticator() { + Set authGroups = ImmutableSet.of("puzzle42"); + Set groups = collector.collectGroups(new AuthenticationResult(UserTestData.createSlarti(), authGroups)); + assertThat(groups, containsInAnyOrder("_authenticated", "puzzle42")); + } + + /** + * Tests {@link GroupCollector#collectGroups(AuthenticationResult)} with groups from db. + */ + @Test + public void testCollectGroupsWithGroupsFromDB() { + Set dbGroups = ImmutableSet.of(new Group("test", "puzzle42")); + when(groupManager.getGroupsForMember("slarti")).thenReturn(dbGroups); + Set groups = collector.collectGroups(new AuthenticationResult(UserTestData.createSlarti())); + assertThat(groups, containsInAnyOrder("_authenticated", "puzzle42")); + } + +/** + * Tests {@link GroupCollector#collectGroups(AuthenticationResult)} with groups from db. + */ + @Test + public void testCollectGroupsWithGroupsFromDBAndAuthenticator() { + Set dbGroups = ImmutableSet.of(new Group("test", "puzzle42")); + Set authGroups = ImmutableSet.of("heartOfGold"); + when(groupManager.getGroupsForMember("slarti")).thenReturn(dbGroups); + Set groups = collector.collectGroups(new AuthenticationResult(UserTestData.createSlarti(), authGroups)); + assertThat(groups, containsInAnyOrder("_authenticated", "puzzle42", "heartOfGold")); + } + +} \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/security/LocalDatabaseSynchronizerTest.java b/scm-webapp/src/test/java/sonia/scm/security/LocalDatabaseSynchronizerTest.java new file mode 100644 index 0000000000..4d29df6c03 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/LocalDatabaseSynchronizerTest.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.security; + +import com.google.common.collect.ImmutableSet; +import org.junit.Test; +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.user.User; +import sonia.scm.user.UserDAO; +import sonia.scm.user.UserManager; +import sonia.scm.user.UserTestData; + +/** + * Unit tests for {@link LocalDatabaseSynchronizer}. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +@RunWith(MockitoJUnitRunner.class) +public class LocalDatabaseSynchronizerTest { + + @Mock + private AdminDetector adminSelector; + + @Mock + private UserManager userManager; + + @Mock + private UserDAO userDAO; + + @InjectMocks + private LocalDatabaseSynchronizer synchronizer; + + /** + * Tests {@link LocalDatabaseSynchronizer#synchronize(User, java.util.Set)}. + */ + @Test + public void testSynchronizeWithoutDBUser() { + User trillian = UserTestData.createTrillian(); + trillian.setType("local"); + synchronizer.synchronize(trillian, ImmutableSet.of()); + verify(userDAO).add(trillian); + } + + /** + * Tests {@link LocalDatabaseSynchronizer#synchronize(sonia.scm.user.User, java.util.Set)}. + */ + @Test + public void testSynchronize() { + User trillian = UserTestData.createTrillian(); + trillian.setDisplayName("Trici"); + trillian.setType("local"); + trillian.setAdmin(false); + trillian.setActive(true); + + User dbTrillian = UserTestData.createTrillian(); + dbTrillian.setType("local"); + dbTrillian.setAdmin(true); + dbTrillian.setActive(false); + + when(userDAO.get(trillian.getId())).thenReturn(dbTrillian); + + synchronizer.synchronize(trillian, ImmutableSet.of()); + assertTrue(trillian.isAdmin()); + assertFalse(trillian.isActive()); + verify(userDAO).modify(trillian); + } + + + +} \ No newline at end of file From 06b67e2c72692daedc90aadd71d7bdae5c70444b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 26 Feb 2017 14:53:03 +0100 Subject: [PATCH 06/56] fixed wrong status code check i DeactivatedUserITCase integration test --- .../src/test/java/sonia/scm/it/DeactivatedUserITCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/test/java/sonia/scm/it/DeactivatedUserITCase.java b/scm-webapp/src/test/java/sonia/scm/it/DeactivatedUserITCase.java index 68fe13b201..61b6e55b34 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/DeactivatedUserITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/DeactivatedUserITCase.java @@ -128,7 +128,7 @@ public class DeactivatedUserITCase "slart123"); assertNotNull(response); - assertEquals(401, response.getStatus()); + assertEquals(403, response.getStatus()); } //~--- fields --------------------------------------------------------------- From a6120f0b169a46ca7f0459692bb286dbc7068955 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 26 Feb 2017 14:54:01 +0100 Subject: [PATCH 07/56] rename SessionStore to CredentialsStore --- .../security/AuthenticationInfoCollector.java | 8 +- ...essionStore.java => CredentialsStore.java} | 26 +++-- .../AuthenticationInfoCollectorTest.java | 2 +- .../scm/security/CredentialsStoreTest.java | 94 +++++++++++++++++++ .../java/sonia/scm/security/ScmRealmTest.java | 2 +- 5 files changed, 120 insertions(+), 12 deletions(-) rename scm-webapp/src/main/java/sonia/scm/security/{SessionStore.java => CredentialsStore.java} (78%) create mode 100644 scm-webapp/src/test/java/sonia/scm/security/CredentialsStoreTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java b/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java index a82352b624..7e2547a4e4 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/AuthenticationInfoCollector.java @@ -59,22 +59,22 @@ public class AuthenticationInfoCollector { private final LocalDatabaseSynchronizer synchronizer; private final GroupCollector groupCollector; - private final SessionStore sessionStore; + private final CredentialsStore sessionStore; /** * Construct a new AuthenticationInfoCollector. * * @param synchronizer local database synchronizer * @param groupCollector groups collector - * @param sessionStore session store + * @param credentialsStore credentials store */ @Inject public AuthenticationInfoCollector( - LocalDatabaseSynchronizer synchronizer, GroupCollector groupCollector, SessionStore sessionStore + LocalDatabaseSynchronizer synchronizer, GroupCollector groupCollector, CredentialsStore credentialsStore ) { this.synchronizer = synchronizer; this.groupCollector = groupCollector; - this.sessionStore = sessionStore; + this.sessionStore = credentialsStore; } /** diff --git a/scm-webapp/src/main/java/sonia/scm/security/SessionStore.java b/scm-webapp/src/main/java/sonia/scm/security/CredentialsStore.java similarity index 78% rename from scm-webapp/src/main/java/sonia/scm/security/SessionStore.java rename to scm-webapp/src/main/java/sonia/scm/security/CredentialsStore.java index 88ae3c0912..eeaef74bf6 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/SessionStore.java +++ b/scm-webapp/src/main/java/sonia/scm/security/CredentialsStore.java @@ -30,26 +30,35 @@ */ package sonia.scm.security; +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Provider; import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authc.UsernamePasswordToken; /** - * + * Stores credentials of the user in the http session of the user. + * * @author Sebastian Sdorra + * @since 1.52 */ -public class SessionStore { +public class CredentialsStore { - private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; + @VisibleForTesting + static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; private final Provider requestProvider; @Inject - public SessionStore(Provider requestProvider) { + public CredentialsStore(Provider requestProvider) { this.requestProvider = requestProvider; } - + + /** + * Extracts the user credentials from token, encrypts them, and stores them in the http session. + * + * @param token username password token + */ public void store(UsernamePasswordToken token) { // store encrypted credentials in session String credentials = token.getUsername(); @@ -59,8 +68,13 @@ public class SessionStore { credentials = credentials.concat(":").concat(new String(password)); } - credentials = CipherUtil.getInstance().encode(credentials); + credentials = encrypt(credentials); requestProvider.get().getSession(true).setAttribute(SCM_CREDENTIALS, credentials); } + @VisibleForTesting + protected String encrypt(String credentials){ + return CipherUtil.getInstance().encode(credentials); + } + } diff --git a/scm-webapp/src/test/java/sonia/scm/security/AuthenticationInfoCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/AuthenticationInfoCollectorTest.java index 31504777fd..8eac89731f 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/AuthenticationInfoCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/AuthenticationInfoCollectorTest.java @@ -66,7 +66,7 @@ public class AuthenticationInfoCollectorTest { private GroupCollector groupCollector; @Mock - private SessionStore sessionStore; + private CredentialsStore sessionStore; @InjectMocks private AuthenticationInfoCollector collector; diff --git a/scm-webapp/src/test/java/sonia/scm/security/CredentialsStoreTest.java b/scm-webapp/src/test/java/sonia/scm/security/CredentialsStoreTest.java new file mode 100644 index 0000000000..ca775dcb95 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/CredentialsStoreTest.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +package sonia.scm.security; + +import com.google.inject.Provider; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.junit.Test; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * Unit tests for {@link CredentialsStore}. + * + * @author Sebastian Sdorra + * @since 1.52 + */ +@RunWith(MockitoJUnitRunner.class) +public class CredentialsStoreTest { + + @Mock + private HttpSession session; + + private CredentialsStore store; + + /** + * Set up object under test. + */ + @Before + public void setUpObjectUnderTest() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getSession(true)).thenReturn(session); + Provider provider = mock(Provider.class); + when(provider.get()).thenReturn(request); + + store = new TestableCredentialsStore(provider); + } + + /** + * Tests {@link CredentialsStore#store(org.apache.shiro.authc.UsernamePasswordToken)}. + */ + @Test + public void testStore() { + store.store(new UsernamePasswordToken("trillian", "trillian123")); + verify(session).setAttribute(CredentialsStore.SCM_CREDENTIALS, "x_trillian:trillian123"); + } + + private static class TestableCredentialsStore extends CredentialsStore { + + public TestableCredentialsStore(Provider requestProvider) { + super(requestProvider); + } + + @Override + protected String encrypt(String credentials) { + return "x_".concat(credentials); + } + + } + +} \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java index 2bbbbdd5e5..fdb8d3d57e 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/ScmRealmTest.java @@ -484,7 +484,7 @@ public class ScmRealmTest ); GroupCollector groupCollector = new GroupCollector(groupManager); - SessionStore sessionStore = new SessionStore(requestProvider); + CredentialsStore sessionStore = new CredentialsStore(requestProvider); AuthenticationInfoCollector authcCollector = new AuthenticationInfoCollector( synchronizer, From 66a0165280911e040e7e82ad3e510688d1227d83 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 25 Apr 2017 21:17:31 +0200 Subject: [PATCH 08/56] fix wrong comments of the AdvancedHttpClient api --- .../java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java | 2 +- .../src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java index dd5ec6f425..fdc779a9f9 100644 --- a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java +++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpRequestWithBody.java @@ -123,7 +123,7 @@ public class AdvancedHttpRequestWithBody } /** - * Transforms the given object to a xml string and set this string as request + * Transforms the given object to a json string and set this string as request * content. * * @param object object to transform diff --git a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java index 1cb087710b..66893cbd68 100644 --- a/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java +++ b/scm-core/src/main/java/sonia/scm/net/ahc/AdvancedHttpResponse.java @@ -253,7 +253,9 @@ public abstract class AdvancedHttpResponse } /** - * Transforms the response content from xml to the given type. + * Transforms the response content to the given type. The method will use + * the {@link ContentTransformer} which is responsible for the the given + * content type. * * @param object type * @param type object type From 1d40b454ecd8758e43c44aba4728b1dd78a0a6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Stefanik?= Date: Tue, 7 Mar 2017 18:53:07 +0000 Subject: [PATCH 09/56] Fix e-mail address validation for t.co, ucla.college and example.xn--p1ai These are all valid domain names that can host e-mail addresses. --- scm-core/src/main/java/sonia/scm/util/ValidationUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-core/src/main/java/sonia/scm/util/ValidationUtil.java b/scm-core/src/main/java/sonia/scm/util/ValidationUtil.java index d0b67c5472..630db7f3d7 100644 --- a/scm-core/src/main/java/sonia/scm/util/ValidationUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/ValidationUtil.java @@ -52,7 +52,7 @@ public final class ValidationUtil /** Field description */ private static final String REGEX_MAIL = - "^[A-z0-9][\\w.-]*@[A-z0-9][\\w\\-\\.]+\\.[A-z0-9]{2,6}$"; + "^[A-z0-9][\\w.-]*@[A-z0-9][\\w\\-\\.]*\\.[A-z0-9][A-z0-9-]+$"; /** Field description */ private static final String REGEX_NAME = From d68e55756133719dd7ca6073ec20a36d057c13ac Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 25 Apr 2017 21:29:34 +0200 Subject: [PATCH 10/56] added more tests for email address validation --- scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java b/scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java index 620c2fae9d..d64cb37457 100644 --- a/scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java @@ -78,6 +78,9 @@ public class ValidationUtilTest assertTrue(ValidationUtil.isMailAddressValid("sdorra@ostfalia.de")); assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@hbk-bs.de")); assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@gmail.com")); + assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@t.co")); + assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@ucla.college")); + assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@example.xn--p1ai")); // false assertFalse(ValidationUtil.isMailAddressValid("ostfalia.de")); From 086084a5f4e33512f002717ebebf55401b0eaecc Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 26 Apr 2017 08:46:53 +0200 Subject: [PATCH 11/56] close branch issue-781 From f4c9c359968796ef54f004e1651224a9c030d3c6 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 26 Apr 2017 11:01:18 +0200 Subject: [PATCH 12/56] added support for gtld email domains, see #909 --- .../test/java/sonia/scm/util/ValidationUtilTest.java | 3 +++ .../webapp/resources/js/override/ext.form.vtypes.js | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java b/scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java index d64cb37457..3eaddf2d36 100644 --- a/scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/util/ValidationUtilTest.java @@ -81,6 +81,9 @@ public class ValidationUtilTest assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@t.co")); assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@ucla.college")); assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@example.xn--p1ai")); + + // issue 909 + assertTrue(ValidationUtil.isMailAddressValid("s.sdorra@scm.solutions")); // false assertFalse(ValidationUtil.isMailAddressValid("ostfalia.de")); diff --git a/scm-webapp/src/main/webapp/resources/js/override/ext.form.vtypes.js b/scm-webapp/src/main/webapp/resources/js/override/ext.form.vtypes.js index 91d5307577..eb751bbbb2 100644 --- a/scm-webapp/src/main/webapp/resources/js/override/ext.form.vtypes.js +++ b/scm-webapp/src/main/webapp/resources/js/override/ext.form.vtypes.js @@ -78,6 +78,14 @@ Ext.apply(Ext.form.VTypes, { return this.name(val); }, - usernameText: 'The username is invalid.' + usernameText: 'The username is invalid.', + + emailRegex: /^[A-z0-9][\w.-]*@[A-z0-9][\w\-\.]*\.[A-z0-9][A-z0-9-]+$/, + + // override extjs email format validation to match backend validation rules + // see https://bitbucket.org/sdorra/scm-manager/issues/909/new-gtld-support + email: function(email) { + return this.emailRegex.test(email); + } }); \ No newline at end of file From 1bab7eac8e956c13dc01a986b0b8c418e2f388ca Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 26 Apr 2017 12:10:39 +0200 Subject: [PATCH 13/56] update svnkit to version 1.8.5-scm1 and use javadoc of javase 7 instead of version 6 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7f83511f51..50eab3e905 100644 --- a/pom.xml +++ b/pom.xml @@ -212,7 +212,7 @@ true true - http://download.oracle.com/javase/6/docs/api/ + http://download.oracle.com/javase/7/docs/api/ http://download.oracle.com/docs/cd/E17802_01/products/products/servlet/2.5/docs/servlet-2_5-mr2/ http://jersey.java.net/nonav/apidocs/${jersey.version}/jersey/ https://google.github.io/guice/api-docs/${guice.version}/javadoc @@ -425,7 +425,7 @@ v4.5.0.201609210915-r-scm1 - 1.8.14-scm1 + 1.8.15-scm1 15.0 From de5e1084e959f4c8fb35523be4ac92c1065969cb Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 9 May 2017 10:44:26 +0200 Subject: [PATCH 14/56] remove jgit repository, because it is not longer required --- scm-plugins/scm-git-plugin/pom.xml | 5 ----- scm-test/pom.xml | 6 ------ 2 files changed, 11 deletions(-) diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 22604f695f..e691c3ec96 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -79,11 +79,6 @@ - - jgit-repository - http://download.eclipse.org/jgit/maven - - maven.scm-manager.org scm-manager release repository diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 80c0b92474..34c3ec1426 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -73,12 +73,6 @@ tmatesoft release repository https://maven.tmatesoft.com/content/repositories/releases - - - jgit-repository - jgit release repository - http://download.eclipse.org/jgit/maven - From 9ffb07aceebdea65d8bf3e3bbdec58aef64dda2b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 9 May 2017 15:18:14 +0200 Subject: [PATCH 15/56] update enunciate to version 2.9.1 --- pom.xml | 2 +- scm-webapp/pom.xml | 52 +++++++++++++++---- scm-webapp/src/main/doc/enunciate.xml | 31 ++++++----- .../resources/AuthenticationResource.java | 4 +- .../resources/ChangePasswordResource.java | 5 +- .../rest/resources/ConfigurationResource.java | 3 -- .../scm/api/rest/resources/GroupResource.java | 5 +- .../scm/api/rest/resources/KeyResource.java | 3 -- .../api/rest/resources/PluginResource.java | 3 -- .../resources/RepositoryImportResource.java | 5 +- .../rest/resources/RepositoryResource.java | 5 +- .../resources/RepositoryRootResource.java | 3 -- .../api/rest/resources/SearchResource.java | 3 -- .../resources/SecuritySystemResource.java | 2 - .../api/rest/resources/SupportResource.java | 3 -- .../scm/api/rest/resources/UserResource.java | 5 +- 16 files changed, 66 insertions(+), 68 deletions(-) diff --git a/pom.xml b/pom.xml index 50eab3e905..e8176da250 100644 --- a/pom.xml +++ b/pom.xml @@ -142,7 +142,7 @@ org.codehaus.mojo animal-sniffer-maven-plugin - 1.9 + 1.15 org.codehaus.mojo.signature diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index bb05eb1847..54ec78d376 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -275,10 +275,9 @@ - org.codehaus.enunciate - enunciate-jersey-rt + com.webcohesion.enunciate + enunciate-jaxrs ${enunciate.version} - jackson-jaxrs @@ -546,7 +545,7 @@ target/scm-it default 2.53.1 - 1.31 + 2.9.1 1.13.1 1.0 3.0.5 @@ -825,8 +824,35 @@ - org.codehaus.enunciate - maven-enunciate-plugin + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + + copy-enunciate-configuration + compile + + copy-resources + + + ${project.build.directory} + + + src/main/doc + true + + **/enunciate.xml + + + + + + + + + + com.webcohesion.enunciate + enunciate-maven-plugin ${enunciate.version} @@ -837,14 +863,20 @@ - src/main/doc/enunciate.xml + ${project.build.directory}/enunciate.xml ${project.build.directory}/restdocs - org.codehaus.enunciate - enunciate-jersey - ${enunciate.version} + com.webcohesion.enunciate + enunciate-top + 2.9.1 + + + com.webcohesion.enunciate + enunciate-swagger + + diff --git a/scm-webapp/src/main/doc/enunciate.xml b/scm-webapp/src/main/doc/enunciate.xml index 504566a55d..19635b46a9 100644 --- a/scm-webapp/src/main/doc/enunciate.xml +++ b/scm-webapp/src/main/doc/enunciate.xml @@ -39,30 +39,33 @@ Description: Enunciate configuration --> - + + SCM-Manager API + + + SCM-Manager API +

This page describes the REST Api of SCM-Manager ${project.version}.

+ ]]> +
+ - + - - - - - + + + - - - - - - + diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java index 36d296e1c5..c0cc127c1b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java @@ -41,6 +41,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; @@ -50,8 +51,6 @@ import org.apache.shiro.authz.Permission; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; -import org.codehaus.enunciate.jaxrs.TypeHint; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -106,7 +105,6 @@ import sonia.scm.security.XsrfCookies; */ @Singleton @Path("authentication") -@ExternallyManagedLifecycle @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public class AuthenticationResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java index 121729f65b..c2ac16eed6 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java @@ -36,13 +36,11 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; +import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; -import org.codehaus.enunciate.jaxrs.TypeHint; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +69,6 @@ import sonia.scm.security.Role; * * @author Sebastian Sdorra */ -@ExternallyManagedLifecycle @Path("action/change-password") public class ChangePasswordResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ConfigurationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ConfigurationResource.java index 6c888470ad..2fb6bdda3d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ConfigurationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ConfigurationResource.java @@ -41,8 +41,6 @@ import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import sonia.scm.config.ScmConfiguration; import sonia.scm.security.Role; import sonia.scm.security.ScmSecurityException; @@ -66,7 +64,6 @@ import javax.ws.rs.core.UriInfo; */ @Singleton @Path("config") -@ExternallyManagedLifecycle public class ConfigurationResource { 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 ce5a26a6a1..afee3eb7e8 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 @@ -37,12 +37,10 @@ package sonia.scm.api.rest.resources; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; -import org.codehaus.enunciate.jaxrs.TypeHint; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import sonia.scm.group.Group; import sonia.scm.group.GroupException; import sonia.scm.group.GroupManager; @@ -75,7 +73,6 @@ import javax.ws.rs.core.UriInfo; */ @Path("groups") @Singleton -@ExternallyManagedLifecycle public class GroupResource extends AbstractManagerResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java index 3a6e10a51f..7668a04b71 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java @@ -37,8 +37,6 @@ import com.google.inject.Inject; import org.apache.shiro.SecurityUtils; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import sonia.scm.security.KeyGenerator; import sonia.scm.security.Role; @@ -56,7 +54,6 @@ import javax.ws.rs.core.MediaType; * @since 1.41 */ @Path("security/key") -@ExternallyManagedLifecycle public class KeyResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java index 02631c39fc..f6980e844d 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java @@ -39,8 +39,6 @@ import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.Singleton; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,7 +78,6 @@ import javax.ws.rs.core.Response.Status; */ @Singleton @Path("plugins") -@ExternallyManagedLifecycle public class PluginResource { 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 65076528d6..4232ecda9d 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 @@ -43,9 +43,6 @@ import com.google.inject.Inject; import org.apache.shiro.SecurityUtils; -import org.codehaus.enunciate.jaxrs.TypeHint; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,6 +71,7 @@ import static com.google.common.base.Preconditions.*; import com.sun.jersey.api.client.ClientResponse.Status; import com.sun.jersey.multipart.FormDataParam; +import com.webcohesion.enunciate.metadata.rs.TypeHint; import java.io.File; import java.io.IOException; @@ -111,7 +109,6 @@ import javax.xml.bind.annotation.XmlRootElement; * @author Sebastian Sdorra */ @Path("import/repositories") -@ExternallyManagedLifecycle public class RepositoryImportResource { 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 afba957adb..be0a2ec603 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 @@ -39,12 +39,10 @@ import com.google.common.base.Strings; import com.google.common.io.Closeables; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; -import org.codehaus.enunciate.jaxrs.TypeHint; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -111,7 +109,6 @@ import javax.ws.rs.core.UriInfo; */ @Singleton @Path("repositories") -@ExternallyManagedLifecycle public class RepositoryResource extends AbstractManagerResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryRootResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryRootResource.java index 1633feb270..056ad7c7db 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryRootResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryRootResource.java @@ -41,8 +41,6 @@ import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.inject.Inject; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryTypePredicate; @@ -74,7 +72,6 @@ import javax.ws.rs.core.MediaType; * * @author Sebastian Sdorra */ -@ExternallyManagedLifecycle @Path("help/repository-root/{type}.html") public class RepositoryRootResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java index 0b01dffeaa..3b4606682a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java @@ -39,8 +39,6 @@ import com.google.common.base.Function; import com.google.inject.Inject; import com.google.inject.Singleton; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import sonia.scm.HandlerEvent; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; @@ -68,7 +66,6 @@ import javax.ws.rs.core.MediaType; */ @Singleton @Path("search") -@ExternallyManagedLifecycle public class SearchResource implements UserListener, GroupListener { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java index b4baee98e6..d97b21110b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java @@ -45,14 +45,12 @@ import sonia.scm.security.SecuritySystem; import javax.ws.rs.Path; import javax.ws.rs.PathParam; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; /** * * @author Sebastian Sdorra */ @Path("security/permission") -@ExternallyManagedLifecycle public class SecuritySystemResource { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java index ac3cf73f86..9a6a5e4348 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java @@ -42,8 +42,6 @@ import com.google.inject.Inject; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import sonia.scm.SCMContextProvider; import sonia.scm.ServletContainerDetector; import sonia.scm.Type; @@ -79,7 +77,6 @@ import javax.ws.rs.core.MediaType; * @author Sebastian Sdorra */ @Path("support") -@ExternallyManagedLifecycle public class SupportResource { 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 ad264fd0a8..e76d185ed2 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 @@ -37,12 +37,10 @@ package sonia.scm.api.rest.resources; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; -import org.codehaus.enunciate.jaxrs.TypeHint; -import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; - import sonia.scm.security.EncryptionHandler; import sonia.scm.security.Role; import sonia.scm.user.User; @@ -78,7 +76,6 @@ import javax.ws.rs.core.UriInfo; */ @Singleton @Path("users") -@ExternallyManagedLifecycle public class UserResource extends AbstractManagerResource { From 42f412faa49bf0794fdf098f7df5d755e49af466 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 9 May 2017 16:06:08 +0200 Subject: [PATCH 16/56] improve rest api documentation of Authentication, ChangePassword, Cipher, Group, Key and Plugin resource --- .../resources/AuthenticationResource.java | 104 +++++--------- .../resources/ChangePasswordResource.java | 19 +-- .../api/rest/resources/CipherResource.java | 13 +- .../scm/api/rest/resources/GroupResource.java | 110 ++++++--------- .../scm/api/rest/resources/KeyResource.java | 14 +- .../api/rest/resources/PluginResource.java | 127 +++++++++--------- 6 files changed, 159 insertions(+), 228 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java index c0cc127c1b..dd3c780cb1 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java @@ -41,6 +41,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; @@ -78,11 +80,9 @@ import sonia.scm.util.HttpUtil; import java.util.Collection; import java.util.Collections; import java.util.List; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; @@ -100,7 +100,8 @@ import javax.xml.bind.annotation.XmlRootElement; import sonia.scm.security.XsrfCookies; /** - * + * Authentication related RESTful Web Service endpoint. + * * @author Sebastian Sdorra */ @Singleton @@ -152,15 +153,8 @@ public class AuthenticationResource //~--- methods -------------------------------------------------------------- /** - * Authenticate a user and return the state of the application.
- *
- *
    - *
  • 200 success
  • - *
  • 400 bad request, required parameter is missing.
  • - *
  • 401 unauthorized, the specified username or password is wrong
  • - *
  • 500 internal server error
  • - *
- * + * Authenticate a user and return the state of the application. + * * @param request the current http request * @param username the username for the authentication * @param password the password for the authentication @@ -171,6 +165,12 @@ public class AuthenticationResource @POST @Path("login") @TypeHint(ScmState.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, required parameter is missing"), + @ResponseCode(code = 401, condition = "unauthorized, the specified username or password is wrong"), + @ResponseCode(code = 500, condition = "internal server error") + }) public Response authenticate(@Context HttpServletRequest request, @FormParam("username") String username, @FormParam("password") String password, @FormParam("rememberMe") @@ -243,13 +243,7 @@ public class AuthenticationResource } /** - * Logout the current user. Returns the current state of the application, - * if public access is enabled.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Logout the current user. Returns the current state of the application, if public access is enabled. * * @param request the current http request * @param response the current http response @@ -259,6 +253,10 @@ public class AuthenticationResource @GET @Path("logout") @TypeHint(ScmState.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) public Response logout(@Context HttpServletRequest request, @Context HttpServletResponse response) { @@ -287,16 +285,8 @@ public class AuthenticationResource //~--- get methods ---------------------------------------------------------- /** - * This method is an alias of the - * {@link #getState(javax.servlet.http.HttpServletRequest)} method. - * The only difference between the methods, - * is that this one could not be used with basic authentication.
- *
- *
    - *
  • 200 success
  • - *
  • 401 unauthorized, user is not authenticated and public access is disabled.
  • - *
  • 500 internal server error
  • - *
+ * This method is an alias of the {@link #getState(HttpServletRequest)} method. + * The only difference between the methods, is that this one could not be used with basic authentication. * * @param request the current http request * @@ -305,19 +295,18 @@ public class AuthenticationResource @GET @Path("state") @TypeHint(ScmState.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "unauthorized, user is not authenticated and public access is disabled"), + @ResponseCode(code = 500, condition = "internal server error") + }) public Response getCurrentState(@Context HttpServletRequest request) { return getState(request); } /** - * Returns the current state of the application.
- *
- *
    - *
  • 200 success
  • - *
  • 401 unauthorized, user is not authenticated and public access is disabled.
  • - *
  • 500 internal server error
  • - *
+ * Returns the current state of the application. * * @param request the current http request * @@ -325,6 +314,11 @@ public class AuthenticationResource */ @GET @TypeHint(ScmState.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 401, condition = "unauthorized, user is not authenticated and public access is disabled"), + @ResponseCode(code = 500, condition = "internal server error") + }) public Response getState(@Context HttpServletRequest request) { Response response; @@ -361,28 +355,12 @@ public class AuthenticationResource //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @return - */ private ScmState createAnonymousState() { return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST); } - /** - * Method description - * - * - * @param securityContext - * - * @param subject - * - * @return - */ private ScmState createState(Subject subject) { PrincipalCollection collection = subject.getPrincipals(); @@ -410,17 +388,6 @@ public class AuthenticationResource return createState(user, groups.getCollection(), builder.build(), ap); } - /** - * Method description - * - * - * @param user - * @param groups - * @param assignedPermissions - * @param availablePermissions - * - * @return - */ private ScmState createState(User user, Collection groups, List assignedPermissions, List availablePermissions) @@ -431,17 +398,6 @@ public class AuthenticationResource availablePermissions); } - /** - * Method description - * - * - * @param request - * @param ex - * @param status - * @param failure - * - * @return - */ private Response handleFailedAuthentication(HttpServletRequest request, AuthenticationException ex, Response.Status status, WUIAuthenticationFailure failure) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java index c2ac16eed6..52b8b02c37 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java @@ -36,6 +36,8 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; @@ -66,7 +68,8 @@ import javax.ws.rs.core.Response; import sonia.scm.security.Role; /** - * + * Resource to change the password of the authenticated user. + * * @author Sebastian Sdorra */ @Path("action/change-password") @@ -98,14 +101,7 @@ public class ChangePasswordResource //~--- methods -------------------------------------------------------------- /** - * Changes the password of the current user.
- *
- * Status codes: - *
    - *
  • 200 success
  • - *
  • 400 bad request, the old password is not correct
  • - *
  • 500 internal server error
  • - *
+ * Changes the password of the current user. * * @param oldPassword old password of the current user * @param newPassword new password for the current user @@ -117,6 +113,11 @@ public class ChangePasswordResource */ @POST @TypeHint(RestActionResult.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the old password is not correct"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response changePassword(@FormParam("old-password") String oldPassword, @FormParam("new-password") String newPassword) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/CipherResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/CipherResource.java index 27de46ce03..bbfdb363c0 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/CipherResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/CipherResource.java @@ -35,6 +35,8 @@ package sonia.scm.api.rest.resources; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import org.apache.shiro.SecurityUtils; @@ -60,12 +62,7 @@ public class CipherResource /** * Encrypts the request body and returns an encrypted string. This method can - * only executed with administration privileges.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * only executed with administration privileges. * * @param value value to encrypt * @@ -73,6 +70,10 @@ public class CipherResource */ @POST @Path("encrypt") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces(MediaType.TEXT_PLAIN) public String encrypt(String value) { 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 afee3eb7e8..808aaa498b 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 @@ -37,6 +37,9 @@ package sonia.scm.api.rest.resources; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.ResponseHeader; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; @@ -68,7 +71,8 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; /** - * + * RESTful Web Service Resource to manage groups and their members. + * * @author Sebastian Sdorra */ @Path("groups") @@ -99,15 +103,7 @@ public class GroupResource //~--- methods -------------------------------------------------------------- /** - * Creates a new group.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 201 create success
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Creates a new group. Note: This method requires admin privileges. * * @param uriInfo current uri informations * @param group the group to be created @@ -115,6 +111,14 @@ public class GroupResource * @return */ @POST + @StatusCodes({ + @ResponseCode(code = 201, condition = "create success", additionalHeaders = { + @ResponseHeader(name = "Location", description = "uri to the created group") + }), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response create(@Context UriInfo uriInfo, Group group) @@ -123,15 +127,7 @@ public class GroupResource } /** - * Deletes a group.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 201 delete success
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Deletes a group. Note: This method requires admin privileges. * * @param name the name of the group to delete. * @@ -139,6 +135,12 @@ public class GroupResource */ @DELETE @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "delete success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Override public Response delete(@PathParam("id") String name) { @@ -146,15 +148,7 @@ public class GroupResource } /** - * Modifies the given group.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 201 update successful
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Modifies the given group. Note: This method requires admin privileges. * * @param uriInfo current uri informations * @param name name of the group to be modified @@ -164,6 +158,12 @@ public class GroupResource */ @PUT @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "update success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response update(@Context UriInfo uriInfo, @@ -175,16 +175,7 @@ public class GroupResource //~--- get methods ---------------------------------------------------------- /** - * Returns a group.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 404 not found, no group with the specified id/name available
  • - *
  • 500 internal server error
  • - *
+ * Fetches a group by its name or id. Note: This method requires admin privileges. * * @param request the current request * @param id the id/name of the group @@ -194,6 +185,12 @@ public class GroupResource @GET @Path("{id}") @TypeHint(Group.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response get(@Context Request request, @PathParam("id") String id) @@ -213,15 +210,7 @@ public class GroupResource } /** - * Returns all groups.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Returns all groups. Note: This method requires admin privileges. * * @param request the current request * @param start the start value for paging @@ -234,6 +223,11 @@ public class GroupResource @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @TypeHint(Group[].class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Override public Response getAll(@Context Request request, @DefaultValue("0") @QueryParam("start") int start, @DefaultValue("-1") @@ -246,14 +240,6 @@ public class GroupResource //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @param items - * - * @return - */ @Override protected GenericEntity> createGenericEntity( Collection items) @@ -264,26 +250,12 @@ public class GroupResource //~--- get methods ---------------------------------------------------------- - /** - * Method description - * - * - * @param group - * - * @return - */ @Override protected String getId(Group group) { return group.getName(); } - /** - * Method description - * - * - * @return - */ @Override protected String getPathPart() { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java index 7668a04b71..6a4c56a643 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/KeyResource.java @@ -34,6 +34,8 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import org.apache.shiro.SecurityUtils; @@ -72,17 +74,15 @@ public class KeyResource //~--- methods -------------------------------------------------------------- /** - * Generates a unique key. This method can only executed with administration - * privileges.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Generates a unique key. Note: This method can only executed with administration privileges. * * @return unique key */ @GET + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces(MediaType.TEXT_PLAIN) public String generateKey() { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java index f6980e844d..0fd7518795 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java @@ -53,6 +53,9 @@ import sonia.scm.plugin.PluginInformationComparator; //~--- JDK imports ------------------------------------------------------------ import com.sun.jersey.multipart.FormDataParam; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; import java.io.IOException; import java.io.InputStream; @@ -73,7 +76,8 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; /** - * + * RESTful Web Service Endpoint to manage plugins. + * * @author Sebastian Sdorra */ @Singleton @@ -104,13 +108,7 @@ public class PluginResource //~--- methods -------------------------------------------------------------- /** - * Installs a plugin from a package.
- *
- *
    - *
  • 200 success
  • - *
  • 412 precondition failed
  • - *
  • 500 internal server error
  • - *
+ * Installs a plugin from a package. * * @param uploadedInputStream * @return @@ -119,6 +117,11 @@ public class PluginResource */ @POST @Path("install-package") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 412, condition = "precondition failed"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response install( @@ -150,35 +153,30 @@ public class PluginResource } /** - * Installs a plugin.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Installs a plugin. * * @param id id of the plugin to be installed * * @return */ @POST + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Path("install/{id}") public Response install(@PathParam("id") String id) { pluginManager.install(id); + // TODO should return 204 no content return Response.ok().build(); } /** * Installs a plugin from a package. This method is a workaround for ExtJS - * file upload, which requires text/html as content-type.
- *
- *
    - *
  • 200 success
  • - *
  • 412 precondition failed
  • - *
  • 500 internal server error
  • - *
+ * file upload, which requires text/html as content-type. * * @param uploadedInputStream * @return @@ -187,6 +185,11 @@ public class PluginResource */ @POST @Path("install-package.html") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 412, condition = "precondition failed"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_HTML) public Response installFromUI( @@ -197,60 +200,62 @@ public class PluginResource } /** - * Uninstalls a plugin.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Uninstalls a plugin. * * @param id id of the plugin to be uninstalled * * @return */ @POST + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Path("uninstall/{id}") public Response uninstall(@PathParam("id") String id) { pluginManager.uninstall(id); + // TODO should return 204 content + // consider to do a uninstall with a delete return Response.ok().build(); } /** - * Updates a plugin.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Updates a plugin. * * @param id id of the plugin to be updated * * @return */ @POST + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Path("update/{id}") public Response update(@PathParam("id") String id) { pluginManager.update(id); + // TODO should return 204 content + // consider to do an update with a put + return Response.ok().build(); } //~--- get methods ---------------------------------------------------------- /** - * Returns all plugins.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Returns all plugins. * * @return all plugins */ @GET + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Collection getAll() { @@ -258,17 +263,16 @@ public class PluginResource } /** - * Returns all available plugins.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Returns all available plugins. * * @return all available plugins */ @GET @Path("available") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Collection getAvailable() { @@ -276,17 +280,16 @@ public class PluginResource } /** - * Returns all plugins which are available for update.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Returns all plugins which are available for update. * * @return all plugins which are available for update */ @GET @Path("updates") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Collection getAvailableUpdates() { @@ -294,17 +297,16 @@ public class PluginResource } /** - * Returns all installed plugins.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Returns all installed plugins. * * @return all installed plugins */ @GET @Path("installed") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Collection getInstalled() { @@ -312,17 +314,16 @@ public class PluginResource } /** - * Returns all plugins for the overview.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Returns all plugins for the overview. * * @return all plugins for the overview */ @GET @Path("overview") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Collection getOverview() { From 9ff0d1863c71368818013f718acf2f8dc1cd3c9c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 10 May 2017 08:56:43 +0200 Subject: [PATCH 17/56] improve rest api documentation of Repository, RepositoryImport, Search, SecuritySystem and User resource --- .../resources/AbstractPermissionResource.java | 83 +++-- .../api/rest/resources/PluginResource.java | 1 + .../resources/RepositoryImportResource.java | 152 +++++----- .../rest/resources/RepositoryResource.java | 287 ++++++++---------- .../api/rest/resources/SearchResource.java | 32 +- .../resources/SecuritySystemResource.java | 24 +- .../scm/api/rest/resources/UserResource.java | 153 +++------- 7 files changed, 322 insertions(+), 410 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractPermissionResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractPermissionResource.java index 29384ba020..e9e63d16d8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractPermissionResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AbstractPermissionResource.java @@ -38,6 +38,10 @@ package sonia.scm.api.rest.resources; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Lists; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.ResponseHeader; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; +import com.webcohesion.enunciate.metadata.rs.TypeHint; import sonia.scm.api.rest.Permission; import sonia.scm.security.AssignedPermission; @@ -114,13 +118,7 @@ public abstract class AbstractPermissionResource //~--- methods -------------------------------------------------------------- /** - * Adds a new permission to the user or group managed by the resource.
- *
- * Status codes: - *
    - *
  • 201 add successful
  • - *
  • 500 internal server error
  • - *
+ * Adds a new permission to the user or group managed by the resource. * * @param uriInfo uri informations * @param permission permission to add @@ -128,6 +126,13 @@ public abstract class AbstractPermissionResource * @return web response */ @POST + @StatusCodes({ + @ResponseCode(code = 201, condition = "creates", additionalHeaders = { + @ResponseHeader(name = "Location", description = "uri to new create permission") + }), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response add(@Context UriInfo uriInfo, Permission permission) { @@ -139,15 +144,7 @@ public abstract class AbstractPermissionResource } /** - * Deletes a permission from the user or group managed by the resource.
- *
- * Status codes: - *
    - *
  • 200 delete successful
  • - *
  • 400 bad request, permission id does not belong to the user or group
  • - *
  • 404 not found, no permission with the specified id available
  • - *
  • 500 internal server error
  • - *
+ * Deletes a permission from the user or group managed by the resource. * * @param id id of the permission * @@ -155,6 +152,13 @@ public abstract class AbstractPermissionResource */ @DELETE @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, permission id does not belong to the user or group"), + @ResponseCode(code = 404, condition = "not found, no permission with the specified id available"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) public Response delete(@PathParam("id") String id) { StoredAssignedPermission sap = getPermission(id); @@ -165,16 +169,7 @@ public abstract class AbstractPermissionResource } /** - * Updates the specified permission on the user or group managed by the - * resource.
- *
- * Status codes: - *
    - *
  • 204 update successful
  • - *
  • 400 bad request, permission id does not belong to the user or group
  • - *
  • 404 not found, no permission with the specified id available
  • - *
  • 500 internal server error
  • - *
+ * Updates the specified permission on the user or group managed by the resource. * * @param id id of the permission * @param permission updated permission @@ -183,6 +178,13 @@ public abstract class AbstractPermissionResource */ @PUT @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, permission id does not belong to the user or group"), + @ResponseCode(code = 404, condition = "not found, no permission with the specified id available"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response update(@PathParam("id") String id, Permission permission) { @@ -197,16 +199,7 @@ public abstract class AbstractPermissionResource //~--- get methods ---------------------------------------------------------- /** - * Returns the {@link Permission} with the specified id.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, permission id does not belong to the user or group
  • - *
  • 404 not found, no permission with the specified id available
  • - *
  • 500 internal server error
  • - *
- * + * Returns the {@link Permission} with the specified id. * * @param id id of the {@link Permission} * @@ -214,6 +207,12 @@ public abstract class AbstractPermissionResource */ @GET @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, permission id does not belong to the user or group"), + @ResponseCode(code = 404, condition = "not found, no permission with the specified id available"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Permission get(@PathParam("id") String id) { @@ -223,17 +222,15 @@ public abstract class AbstractPermissionResource } /** - * Returns all permissions of the user or group managed by the resource.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 500 internal server error
  • - *
+ * Returns all permissions of the user or group managed by the resource. * * @return all permissions of the user or group */ @GET + @StatusCodes({ + @ResponseCode(code = 204, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public List getAll() { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java index 0fd7518795..54c9369a57 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/PluginResource.java @@ -111,6 +111,7 @@ public class PluginResource * Installs a plugin from a package. * * @param uploadedInputStream + * * @return * * @throws IOException 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 4232ecda9d..9382f58c5c 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 @@ -71,6 +71,9 @@ import static com.google.common.base.Preconditions.*; import com.sun.jersey.api.client.ClientResponse.Status; import com.sun.jersey.multipart.FormDataParam; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.ResponseHeader; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import java.io.File; @@ -139,17 +142,8 @@ public class RepositoryImportResource /** * Imports a repository type specific bundle. The bundle file is uploaded to * the server which is running scm-manager. After the upload has finished, the - * bundle file is passed to the {@link UnbundleCommandBuilder}. This method - * requires admin privileges.
- * - * Status codes: - *
    - *
  • 201 created
  • - *
  • 400 bad request, the import bundle feature is not supported by this - * type of repositories or the parameters are not valid.
  • - *
  • 500 internal server error
  • - *
  • 409 conflict, a repository with the name already exists.
  • - *
+ * bundle file is passed to the {@link UnbundleCommandBuilder}. Note: This method + * requires admin privileges. * * @param uriInfo uri info * @param type repository type @@ -157,12 +151,23 @@ public class RepositoryImportResource * @param inputStream input bundle * @param compressed true if the bundle is gzip compressed * - * @return empty response with location header which points to the imported - * repository + * @return empty response with location header which points to the imported repository * @since 1.43 */ @POST @Path("{type}/bundle") + @StatusCodes({ + @ResponseCode(code = 201, condition = "created", additionalHeaders = { + @ResponseHeader(name = "Location", description = "uri to the imported repository") + }), + @ResponseCode( + code = 400, + condition = "bad request, the import bundle feature is not supported by this type of repositories or the parameters are not valid" + ), + @ResponseCode(code = 409, condition = "conflict, a repository with the name already exists"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes(MediaType.MULTIPART_FORM_DATA) public Response importFromBundle(@Context UriInfo uriInfo, @PathParam("type") String type, @FormDataParam("name") String name, @@ -179,18 +184,8 @@ public class RepositoryImportResource * This method works exactly like * {@link #importFromBundle(UriInfo, String, String, InputStream)}, but this * method returns an html content-type. The method exists only for a - * workaround of the javascript ui extjs. This method requires admin - * privileges.
- * - * Status codes: - *
    - *
  • 201 created
  • - *
  • 400 bad request, the import bundle feature is not supported by this - * type of repositories or the parameters are not valid.
  • - *
  • 500 internal server error
  • - *
  • 409 conflict, a repository with the name already exists.
  • - *
- * + * workaround of the javascript ui extjs. Note: This method requires admin + * privileges. * * @param type repository type * @param name name of the repository @@ -203,6 +198,16 @@ public class RepositoryImportResource */ @POST @Path("{type}/bundle.html") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode( + code = 400, + condition = "bad request, the import bundle feature is not supported by this type of repositories or the parameters are not valid" + ), + @ResponseCode(code = 409, condition = "conflict, a repository with the name already exists"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(RestActionUploadResult.class) @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_HTML) public Response importFromBundleUI(@PathParam("type") String type, @@ -231,16 +236,7 @@ public class RepositoryImportResource * Imports a external repository which is accessible via url. The method can * only be used, if the repository type supports the {@link Command#PULL}. The * method will return a location header with the url to the imported - * repository. This method requires admin privileges.
- * - * Status codes: - *
    - *
  • 201 created
  • - *
  • 400 bad request, the import by url feature is not supported by this - * type of repositories or the parameters are not valid.
  • - *
  • 409 conflict, a repository with the name already exists.
  • - *
  • 500 internal server error
  • - *
+ * repository. Note: This method requires admin privileges. * * @param uriInfo uri info * @param type repository type @@ -252,6 +248,18 @@ public class RepositoryImportResource */ @POST @Path("{type}/url") + @StatusCodes({ + @ResponseCode(code = 201, condition = "created", additionalHeaders = { + @ResponseHeader(name = "Location", description = "uri to the imported repository") + }), + @ResponseCode( + code = 400, + condition = "bad request, the import feature is not supported by this type of repositories or the parameters are not valid" + ), + @ResponseCode(code = 409, condition = "conflict, a repository with the name already exists"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response importFromUrl(@Context UriInfo uriInfo, @PathParam("type") String type, UrlImportRequest request) @@ -295,15 +303,7 @@ public class RepositoryImportResource /** * Imports repositories of the given type from the configured repository - * directory. This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 200 ok, successful
  • - *
  • 400 bad request, the import feature is not - * supported by this type of repositories.
  • - *
  • 500 internal server error
  • - *
+ * directory. Note: This method requires admin privileges. * * @param type repository type * @@ -311,6 +311,14 @@ public class RepositoryImportResource */ @POST @Path("{type}") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode( + code = 400, + condition = "bad request, the import feature is not supported by this type of repositories" + ), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(Repository[].class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response importRepositories(@PathParam("type") String type) @@ -330,19 +338,19 @@ public class RepositoryImportResource /** * Imports repositories of all supported types from the configured repository - * directories. This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 200 ok, successful
  • - *
  • 400 bad request, the import feature is not - * supported by this type of repositories.
  • - *
  • 500 internal server error
  • - *
+ * directories. Note: This method requires admin privileges. * * @return imported repositories */ @POST + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode( + code = 400, + condition = "bad request, the import feature is not supported by this type of repositories" + ), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(Repository[].class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response importRepositories() @@ -368,15 +376,7 @@ public class RepositoryImportResource /** * Imports repositories of the given type from the configured repository * directory. Returns a list of successfully imported directories and a list - * of failed directories. This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 200 ok, successful
  • - *
  • 400 bad request, the import feature is not - * supported by this type of repositories.
  • - *
  • 500 internal server error
  • - *
+ * of failed directories. Note: This method requires admin privileges. * * @param type repository type * @@ -385,6 +385,14 @@ public class RepositoryImportResource */ @POST @Path("{type}/directory") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode( + code = 400, + condition = "bad request, the import feature is not supported by this type of repositories" + ), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(ImportResult.class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response importRepositoriesFromDirectory( @@ -453,22 +461,20 @@ public class RepositoryImportResource /** * Returns a list of repository types, which support the directory import - * feature. - * - * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 200 ok, successful
  • - *
  • 400 bad request, the import feature is not - * supported by this type of repositories.
  • - *
  • 500 internal server error
  • - *
+ * feature. Note: This method requires admin privileges. * * @return list of repository types */ @GET @TypeHint(Type[].class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode( + code = 400, + condition = "bad request, the import feature is not supported by this type of repositories" + ), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response getImportableTypes() { 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 be0a2ec603..16168aefc7 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 @@ -39,6 +39,9 @@ import com.google.common.base.Strings; import com.google.common.io.Closeables; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.ResponseHeader; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; @@ -104,13 +107,13 @@ import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.UriInfo; /** - * + * Repository related RESTful Web Service Endpoint. + * * @author Sebastian Sdorra */ @Singleton @Path("repositories") -public class RepositoryResource - extends AbstractManagerResource +public class RepositoryResource extends AbstractManagerResource { /** Field description */ @@ -147,22 +150,22 @@ public class RepositoryResource //~--- methods -------------------------------------------------------------- /** - * Creates a new repository.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 201 create success
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Creates a new repository.Note: This method requires admin privileges. * * @param uriInfo current uri informations * @param repository the repository to be created * - * @return + * @return empty response with location header to the new repository */ @POST + @StatusCodes({ + @ResponseCode(code = 201, condition = "success", additionalHeaders = { + @ResponseHeader(name = "Location", description = "uri to the new created repository") + }), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response create(@Context UriInfo uriInfo, Repository repository) @@ -171,19 +174,7 @@ public class RepositoryResource } /** - * Deletes a repository.
- * This method requires owner privileges.
- *
- * Status codes: - *
    - *
  • 201 delete success
  • - *
  • 403 forbidden, the current user has no owner privileges
  • - *
  • - * 412 forbidden, the repository is not archived, - * this error occurs only with enabled repository archive. - *
  • - *
  • 500 internal server error
  • - *
+ * Deletes a repository. Note: This method requires owner privileges. * * @param id the id of the repository to delete. * @@ -191,6 +182,17 @@ public class RepositoryResource */ @DELETE @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "delete success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no owner privileges"), + @ResponseCode(code = 404, condition = "could not find repository"), + @ResponseCode( + code = 412, + condition = "precondition failed, the repository is not archived, this error occurs only with enabled repository archive" + ), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Override public Response delete(@PathParam("id") String id) { @@ -232,20 +234,20 @@ public class RepositoryResource } /** - * Re run repository health checks.
- * Status codes: - *
    - *
  • 201 re run success
  • - *
  • 403 forbidden, the current user has no owner privileges
  • - *
  • 404 could not find repository
  • - *
  • 500 internal server error
  • - *
+ * Re run repository health checks. * * @param id id of the repository * * @return */ @POST + @StatusCodes({ + @ResponseCode(code = 200, condition = "re run success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no owner privileges"), + @ResponseCode(code = 404, condition = "could not find repository"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Path("{id}/healthcheck") public Response runHealthChecks(@PathParam("id") String id) { @@ -254,6 +256,7 @@ public class RepositoryResource try { healthChecker.check(id); + // TODO should return 204 instead of 200 response = Response.ok().build(); } catch (RepositoryNotFoundException ex) @@ -276,15 +279,7 @@ public class RepositoryResource } /** - * Modifies the given repository.
- * This method requires owner privileges.
- *
- * Status codes: - *
    - *
  • 201 update successful
  • - *
  • 403 forbidden, the current user has no owner privileges
  • - *
  • 500 internal server error
  • - *
+ * Modifies the given repository. Note: This method requires owner privileges. * * @param uriInfo current uri informations * @param id id of the repository to be modified @@ -294,10 +289,16 @@ public class RepositoryResource */ @PUT @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "update successful"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no owner privileges"), + @ResponseCode(code = 404, condition = "could not find repository"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override - public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, - Repository repository) + public Response update(@Context UriInfo uriInfo, @PathParam("id") String id, Repository repository) { return super.update(uriInfo, id, repository); } @@ -305,14 +306,7 @@ public class RepositoryResource //~--- get methods ---------------------------------------------------------- /** - * Returns the {@link Repository} with the specified id.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 404 not found, no repository with the specified id available
  • - *
  • 500 internal server error
  • - *
+ * Returns the {@link Repository} with the specified id. * * @param request the current request * @param id the id/name of the user @@ -322,6 +316,11 @@ public class RepositoryResource @GET @Path("{id}") @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 404, condition = "not found, no repository with the specified id available"), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(Repository.class) @Override public Response get(@Context Request request, @PathParam("id") String id) @@ -330,13 +329,7 @@ public class RepositoryResource } /** - * Returns all repositories.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 500 internal server error
  • - *
+ * Returns all repositories. * * @param request the current request * @param start the start value for paging @@ -348,6 +341,10 @@ public class RepositoryResource */ @GET @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(Repository[].class) @Override public Response getAll(@Context Request request, @DefaultValue("0") @@ -360,16 +357,7 @@ public class RepositoryResource } /** - * Returns a annotate/blame view for the given path.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, the blame feature is not - * supported by this type of repositories.
  • - *
  • 404 not found, if the repository or the path could not be found
  • - *
  • 500 internal server error
  • - *
+ * Returns a annotate/blame view for the given path. * * @param id the id of the repository * @param revision the revision of the file @@ -382,6 +370,12 @@ public class RepositoryResource */ @GET @Path("{id}/blame") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the blame feature is not supported by this type of repositories."), + @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(BlameResult.class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response getBlame(@PathParam("id") String id, @@ -435,16 +429,7 @@ public class RepositoryResource } /** - * Returns all {@link Branches} of a repository.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, the content feature is not - * supported by this type of repositories.
  • - *
  • 404 not found, if the repository or the path could not be found
  • - *
  • 500 internal server error
  • - *
+ * Returns all {@link Branches} of a repository. * * @param id the id of the repository * @@ -457,6 +442,14 @@ public class RepositoryResource */ @GET @Path("{id}/branches") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the branch feature is not supported by this type of repositories."), + @ResponseCode(code = 404, condition = "not found, the repository could not be found"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(Branches.class) + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response getBranches(@PathParam("id") String id) throws RepositoryException, IOException { @@ -495,16 +488,7 @@ public class RepositoryResource } /** - * Returns a list of folders and files for the given folder.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, the browse feature is not - * supported by this type of repositories.
  • - *
  • 404 not found, if the repository or the path could not be found
  • - *
  • 500 internal server error
  • - *
+ * Returns a list of folders and files for the given folder. * * @param id the id of the repository * @param revision the revision of the file @@ -520,6 +504,12 @@ public class RepositoryResource */ @GET @Path("{id}/browse") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the browse feature is not supported by this type of repositories."), + @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(BrowserResult.class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) //J- @@ -586,15 +576,7 @@ public class RepositoryResource } /** - * Returns the {@link Repository} with the specified type and name.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 404 not found, - * no repository with the specified type and name available
  • - *
  • 500 internal server error
  • - *
+ * Returns the {@link Repository} with the specified type and name. * * @param type the type of the repository * @param name the name of the repository @@ -603,8 +585,13 @@ public class RepositoryResource */ @GET @Path("{type: [a-z]+}/{name: .*}") - @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 404, condition = "not found, no repository with the specified type and name available"), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(Repository.class) + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response getByTypeAndName(@PathParam("type") String type, @PathParam("name") String name) { @@ -626,17 +613,7 @@ public class RepositoryResource /** * Returns the {@link Changeset} from the given repository - * with the specified revision.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, the changeset feature is not - * supported by this type of repositories.
  • - *
  • 404 not found, if the repository or - * the revision could not be found
  • - *
  • 500 internal server error
  • - *
+ * with the specified revision. * * @param id the id of the repository * @param revision the revision of the changeset @@ -648,6 +625,14 @@ public class RepositoryResource */ @GET @Path("{id}/changeset/{revision}") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the changeset feature is not supported by this type of repositories."), + @ResponseCode(code = 404, condition = "not found, the repository or the revision could not be found"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(Changeset.class) + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response getChangeset(@PathParam("id") String id, @PathParam("revision") String revision) throws IOException, RepositoryException @@ -700,16 +685,7 @@ public class RepositoryResource } /** - * Returns a list of {@link Changeset} for the given repository.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, the changeset feature is not - * supported by this type of repositories.
  • - *
  • 404 not found, if the repository or the path could not be found
  • - *
  • 500 internal server error
  • - *
+ * Returns a list of {@link Changeset} for the given repository. * * @param id the id of the repository * @param path path of a file @@ -725,6 +701,12 @@ public class RepositoryResource */ @GET @Path("{id}/changesets") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the changeset feature is not supported by this type of repositories."), + @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(ChangesetPagingResult.class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) //J- @@ -793,16 +775,7 @@ public class RepositoryResource } /** - * Returns the content of a file.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, the content feature is not - * supported by this type of repositories.
  • - *
  • 404 not found, if the repository or the path could not be found
  • - *
  • 500 internal server error
  • - *
+ * Returns the content of a file. * * @param id the id of the repository * @param revision the revision of the file @@ -812,14 +785,20 @@ public class RepositoryResource */ @GET @Path("{id}/content") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the content feature is not supported by this type of repositories."), + @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(StreamingOutput.class) @Produces({ MediaType.APPLICATION_OCTET_STREAM }) public Response getContent(@PathParam("id") String id, @QueryParam("revision") String revision, @QueryParam("path") String path) { - Response response = null; - StreamingOutput output = null; - RepositoryService service = null; + Response response; + StreamingOutput output; + RepositoryService service; try { @@ -864,16 +843,7 @@ public class RepositoryResource } /** - * Returns the modifications of a {@link Changeset}.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, the content feature is not - * supported by this type of repositories.
  • - *
  • 404 not found, if the repository or the path could not be found
  • - *
  • 500 internal server error
  • - *
+ * Returns the modifications of a {@link Changeset}. * * @param id the id of the repository * @param revision the revision of the file @@ -887,6 +857,12 @@ public class RepositoryResource */ @GET @Path("{id}/diff") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the diff feature is not supported by this type of repositories."), + @ResponseCode(code = 404, condition = "not found, the repository or the path could not be found"), + @ResponseCode(code = 500, condition = "internal server error") + }) @TypeHint(DiffStreamingOutput.class) @Produces(MediaType.APPLICATION_OCTET_STREAM) public Response getDiff(@PathParam("id") String id, @@ -903,8 +879,8 @@ public class RepositoryResource */ HttpUtil.checkForCRLFInjection(revision); - RepositoryService service = null; - Response response = null; + RepositoryService service; + Response response; try { @@ -952,16 +928,7 @@ public class RepositoryResource } /** - * Returns all {@link Tags} of a repository.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 400 bad request, the content feature is not - * supported by this type of repositories.
  • - *
  • 404 not found, if the repository or the path could not be found
  • - *
  • 500 internal server error
  • - *
+ * Returns all {@link Tags} of a repository. * * @param id the id of the repository * @@ -974,6 +941,14 @@ public class RepositoryResource */ @GET @Path("{id}/tags") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 400, condition = "bad request, the tag feature is not supported by this type of repositories."), + @ResponseCode(code = 404, condition = "not found, the repository could not be found"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(Tags.class) + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response getTags(@PathParam("id") String id) throws RepositoryException, IOException { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java index 3b4606682a..3c7c9b8785 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java @@ -38,6 +38,8 @@ package sonia.scm.api.rest.resources; import com.google.common.base.Function; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import sonia.scm.HandlerEvent; import sonia.scm.cache.Cache; @@ -61,7 +63,9 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; /** - * + * RESTful Web Service Resource to search users and groups. This endpoint can be used to implement typeahead input + * fields for permissions. + * * @author Sebastian Sdorra */ @Singleton @@ -138,12 +142,7 @@ public class SearchResource implements UserListener, GroupListener } /** - * Returns a list of groups found by the given search string.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Returns a list of groups found by the given search string. * * @param queryString the search string * @@ -151,6 +150,10 @@ public class SearchResource implements UserListener, GroupListener */ @GET @Path("groups") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public SearchResults searchGroups(@QueryParam("query") String queryString) { @@ -174,12 +177,7 @@ public class SearchResource implements UserListener, GroupListener } /** - * Returns a list of users found by the given search string.
- *
- *
    - *
  • 200 success
  • - *
  • 500 internal server error
  • - *
+ * Returns a list of users found by the given search string. * * @param queryString the search string * @@ -187,6 +185,10 @@ public class SearchResource implements UserListener, GroupListener */ @GET @Path("users") + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public SearchResults searchUsers(@QueryParam("query") String queryString) { @@ -208,8 +210,8 @@ public class SearchResource implements UserListener, GroupListener //~--- fields --------------------------------------------------------------- /** Field description */ - private SearchHandler groupSearchHandler; + private final SearchHandler groupSearchHandler; /** Field description */ - private SearchHandler userSearchHandler; + private final SearchHandler userSearchHandler; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java index d97b21110b..f9e95232db 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SecuritySystemResource.java @@ -47,7 +47,8 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; /** - * + * Resource for managing system security permissions. + * * @author Sebastian Sdorra */ @Path("security/permission") @@ -72,31 +73,28 @@ public class SecuritySystemResource //~--- get methods ---------------------------------------------------------- /** - * Method description + * Returns group permission sub resource. * + * @param group name of group * - * @param group - * - * @return + * @return sub resource */ @Path("group/{group}") - public GroupPermissionResource getGroupSubResource( - @PathParam("group") String group) + public GroupPermissionResource getGroupSubResource(@PathParam("group") String group) { return new GroupPermissionResource(system, group); } /** - * Method description + * Returns user permission sub resource. * * - * @param user + * @param user name of user * - * @return + * @return sub resource */ @Path("user/{user}") - public UserPermissionResource getUserSubResource( - @PathParam("user") String user) + public UserPermissionResource getUserSubResource(@PathParam("user") String user) { return new UserPermissionResource(system, user); } @@ -104,5 +102,5 @@ public class SecuritySystemResource //~--- fields --------------------------------------------------------------- /** Field description */ - private SecuritySystem system; + private final SecuritySystem system; } 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 e76d185ed2..ed384071b1 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 @@ -37,6 +37,9 @@ package sonia.scm.api.rest.resources; import com.google.inject.Inject; import com.google.inject.Singleton; +import com.webcohesion.enunciate.metadata.rs.ResponseCode; +import com.webcohesion.enunciate.metadata.rs.ResponseHeader; +import com.webcohesion.enunciate.metadata.rs.StatusCodes; import com.webcohesion.enunciate.metadata.rs.TypeHint; import org.apache.shiro.SecurityUtils; @@ -71,7 +74,8 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; /** - * + * RESTful Web Service Resource to manage users. + * * @author Sebastian Sdorra */ @Singleton @@ -96,8 +100,7 @@ public class UserResource extends AbstractManagerResource * @param securityContextProvider */ @Inject - public UserResource(UserManager userManager, - EncryptionHandler encryptionHandler) + public UserResource(UserManager userManager, EncryptionHandler encryptionHandler) { super(userManager); this.encryptionHandler = encryptionHandler; @@ -106,15 +109,7 @@ public class UserResource extends AbstractManagerResource //~--- methods -------------------------------------------------------------- /** - * Creates a new user.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 201 create success
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Creates a new user. Note: This method requires admin privileges. * * @param uriInfo current uri informations * @param user the user to be created @@ -122,6 +117,14 @@ public class UserResource extends AbstractManagerResource * @return */ @POST + @StatusCodes({ + @ResponseCode(code = 201, condition = "create success", additionalHeaders = { + @ResponseHeader(name = "Location", description = "uri to the created group") + }), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response create(@Context UriInfo uriInfo, User user) @@ -130,15 +133,7 @@ public class UserResource extends AbstractManagerResource } /** - * Deletes a user.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 201 delete success
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Deletes a user. Note: This method requires admin privileges. * * @param name the name of the user to delete. * @@ -146,6 +141,12 @@ public class UserResource extends AbstractManagerResource */ @DELETE @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "delete success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Override public Response delete(@PathParam("id") String name) { @@ -153,15 +154,7 @@ public class UserResource extends AbstractManagerResource } /** - * Modifies the given user.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 201 update successful
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Modifies the given user. Note: This method requires admin privileges. * * @param uriInfo current uri informations * @param name name of the user to be modified @@ -171,6 +164,12 @@ public class UserResource extends AbstractManagerResource */ @PUT @Path("{id}") + @StatusCodes({ + @ResponseCode(code = 204, condition = "update success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) + @TypeHint(TypeHint.NO_CONTENT.class) @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response update(@Context UriInfo uriInfo, @@ -182,16 +181,7 @@ public class UserResource extends AbstractManagerResource //~--- get methods ---------------------------------------------------------- /** - * Returns a user.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 404 not found, no user with the specified id/name available
  • - *
  • 500 internal server error
  • - *
+ * Returns a user. Note: This method requires admin privileges. * * @param request the current request * @param id the id/name of the user @@ -201,6 +191,12 @@ public class UserResource extends AbstractManagerResource @GET @Path("{id}") @TypeHint(User.class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 404, condition = "not found, no group with the specified id/name available"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response get(@Context Request request, @PathParam("id") String id) @@ -220,15 +216,7 @@ public class UserResource extends AbstractManagerResource } /** - * Returns all users.
- * This method requires admin privileges.
- *
- * Status codes: - *
    - *
  • 200 get successful
  • - *
  • 403 forbidden, the current user has no admin privileges
  • - *
  • 500 internal server error
  • - *
+ * Returns all users. Note: This method requires admin privileges. * * @param request the current request * @param start the start value for paging @@ -240,6 +228,11 @@ public class UserResource extends AbstractManagerResource */ @GET @TypeHint(User[].class) + @StatusCodes({ + @ResponseCode(code = 200, condition = "success"), + @ResponseCode(code = 403, condition = "forbidden, the current user has no admin privileges"), + @ResponseCode(code = 500, condition = "internal server error") + }) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response getAll(@Context Request request, @DefaultValue("0") @@ -253,14 +246,6 @@ public class UserResource extends AbstractManagerResource //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @param items - * - * @return - */ @Override protected GenericEntity> createGenericEntity( Collection items) @@ -269,24 +254,12 @@ public class UserResource extends AbstractManagerResource ; } - /** - * Method description - * - * - * @param user - */ @Override protected void preCreate(User user) { encryptPassword(user); } - - /** - * Method description - * - * - * @param user - */ + @Override protected void preUpate(User user) { @@ -303,14 +276,6 @@ public class UserResource extends AbstractManagerResource } } - /** - * Method description - * - * - * @param users - * - * @return - */ @Override protected Collection prepareForReturn(Collection users) { @@ -325,14 +290,6 @@ public class UserResource extends AbstractManagerResource return users; } - /** - * Method description - * - * - * @param user - * - * @return - */ @Override protected User prepareForReturn(User user) { @@ -341,42 +298,18 @@ public class UserResource extends AbstractManagerResource return user; } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param user - * - * @return - */ @Override protected String getId(User user) { return user.getName(); } - /** - * Method description - * - * - * @return - */ @Override protected String getPathPart() { return PATH_PART; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param user - */ private void encryptPassword(User user) { String password = user.getPassword(); From 059b68125318597e2f4ab7616cef03ac652a697c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 10 May 2017 08:57:42 +0200 Subject: [PATCH 18/56] modify rest api assembly to match packaging before enunciate upgrade --- scm-webapp/pom.xml | 2 +- scm-webapp/src/main/doc/assembly.xml | 2 +- scm-webapp/src/main/doc/enunciate.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 54ec78d376..fbc35f3cbd 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -864,7 +864,7 @@ ${project.build.directory}/enunciate.xml - ${project.build.directory}/restdocs + ${project.build.directory} diff --git a/scm-webapp/src/main/doc/assembly.xml b/scm-webapp/src/main/doc/assembly.xml index 93c3f88ba0..9b1a1af5de 100644 --- a/scm-webapp/src/main/doc/assembly.xml +++ b/scm-webapp/src/main/doc/assembly.xml @@ -46,7 +46,7 @@ - target/restdocs + target/apidocs false . 0755 diff --git a/scm-webapp/src/main/doc/enunciate.xml b/scm-webapp/src/main/doc/enunciate.xml index 19635b46a9..9bed97455a 100644 --- a/scm-webapp/src/main/doc/enunciate.xml +++ b/scm-webapp/src/main/doc/enunciate.xml @@ -48,7 +48,7 @@ SCM-Manager API -

This page describes the REST Api of SCM-Manager ${project.version}.

+

This page describes the RESTful Web Service API of SCM-Manager ${project.version}.

]]>
From 9c2265e9567ac1c73f6afae15384fec7b2b9862f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 10 May 2017 09:37:31 +0200 Subject: [PATCH 19/56] fix conflict between rest- and apidocs --- scm-webapp/pom.xml | 1 + scm-webapp/src/main/doc/assembly.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index fbc35f3cbd..e1ee2a75ea 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -865,6 +865,7 @@ ${project.build.directory}/enunciate.xml ${project.build.directory} + restdocs diff --git a/scm-webapp/src/main/doc/assembly.xml b/scm-webapp/src/main/doc/assembly.xml index 9b1a1af5de..93c3f88ba0 100644 --- a/scm-webapp/src/main/doc/assembly.xml +++ b/scm-webapp/src/main/doc/assembly.xml @@ -46,7 +46,7 @@ - target/apidocs + target/restdocs false . 0755 From 68903ea5af4c960caefe7f2c4ef08c132c47b8dc Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 10 May 2017 09:46:54 +0200 Subject: [PATCH 20/56] [maven-release-plugin] prepare release 1.52 --- maven/pom.xml | 4 ++-- maven/scm-maven-plugin/pom.xml | 4 ++-- maven/scm-plugin-archetype/pom.xml | 4 ++-- pom.xml | 4 ++-- scm-clients/pom.xml | 6 +++--- scm-clients/scm-cli-client/pom.xml | 6 +++--- scm-clients/scm-client-api/pom.xml | 4 ++-- scm-clients/scm-client-impl/pom.xml | 8 ++++---- scm-core/pom.xml | 4 ++-- scm-dao-orientdb/pom.xml | 8 ++++---- scm-dao-xml/pom.xml | 8 ++++---- scm-plugin-backend/pom.xml | 6 +++--- scm-plugins/pom.xml | 8 ++++---- scm-plugins/scm-git-plugin/pom.xml | 6 +++--- scm-plugins/scm-hg-plugin/pom.xml | 6 +++--- scm-plugins/scm-svn-plugin/pom.xml | 6 +++--- scm-samples/pom.xml | 4 ++-- scm-samples/scm-sample-auth/pom.xml | 6 +++--- scm-samples/scm-sample-hello/pom.xml | 6 +++--- scm-server/pom.xml | 4 ++-- scm-test/pom.xml | 6 +++--- scm-webapp/pom.xml | 24 ++++++++++++------------ support/pom.xml | 4 ++-- support/scm-support-btrace/pom.xml | 6 +++--- 24 files changed, 76 insertions(+), 76 deletions(-) diff --git a/maven/pom.xml b/maven/pom.xml index a8f054bae4..1c857c89de 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52-SNAPSHOT + 1.52 sonia.scm.maven scm-maven-plugins pom - 1.52-SNAPSHOT + 1.52 scm-maven-plugins diff --git a/maven/scm-maven-plugin/pom.xml b/maven/scm-maven-plugin/pom.xml index 17818a9041..284ab3b4da 100644 --- a/maven/scm-maven-plugin/pom.xml +++ b/maven/scm-maven-plugin/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.52-SNAPSHOT + 1.52 sonia.scm.maven scm-maven-plugin - 1.52-SNAPSHOT + 1.52 maven-plugin scm-maven-plugin diff --git a/maven/scm-plugin-archetype/pom.xml b/maven/scm-plugin-archetype/pom.xml index f074b7b141..c92ed9d0b0 100644 --- a/maven/scm-plugin-archetype/pom.xml +++ b/maven/scm-plugin-archetype/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.52-SNAPSHOT + 1.52 sonia.scm.maven scm-plugin-archetype - 1.52-SNAPSHOT + 1.52 scm-plugin-archetype diff --git a/pom.xml b/pom.xml index e8176da250..0ca87aef4f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ sonia.scm scm pom - 1.52-SNAPSHOT + 1.52 The easiest way to share your Git, Mercurial and Subversion repositories over http. @@ -36,7 +36,7 @@ scm:hg:http://bitbucket.org/sdorra/scm-manager scm:hg:https://bitbucket.org/sdorra/scm-manager http://bitbucket.org/sdorra/scm-manager - HEAD + 1.52 diff --git a/scm-clients/pom.xml b/scm-clients/pom.xml index d391cd92d1..0735a002b1 100644 --- a/scm-clients/pom.xml +++ b/scm-clients/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52-SNAPSHOT + 1.52 sonia.scm.clients scm-clients pom - 1.52-SNAPSHOT + 1.52 scm-clients @@ -32,7 +32,7 @@ scm-core sonia.scm jar - 1.52-SNAPSHOT + 1.52 shiro-core diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml index 6ba237c61d..1ed1c9a8c9 100644 --- a/scm-clients/scm-cli-client/pom.xml +++ b/scm-clients/scm-cli-client/pom.xml @@ -6,12 +6,12 @@ scm-clients sonia.scm.clients - 1.52-SNAPSHOT + 1.52 sonia.scm.clients scm-cli-client - 1.52-SNAPSHOT + 1.52 scm-cli-client @@ -34,7 +34,7 @@ sonia.scm.clients scm-client-impl - 1.52-SNAPSHOT + 1.52 diff --git a/scm-clients/scm-client-api/pom.xml b/scm-clients/scm-client-api/pom.xml index d2e8704321..4cf585a375 100644 --- a/scm-clients/scm-client-api/pom.xml +++ b/scm-clients/scm-client-api/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.52-SNAPSHOT + 1.52 sonia.scm.clients scm-client-api jar - 1.52-SNAPSHOT + 1.52 scm-client-api diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml index 594afc25ad..1b1c6a7051 100644 --- a/scm-clients/scm-client-impl/pom.xml +++ b/scm-clients/scm-client-impl/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.52-SNAPSHOT + 1.52 sonia.scm.clients scm-client-impl jar - 1.52-SNAPSHOT + 1.52 scm-client-impl @@ -36,7 +36,7 @@ sonia.scm.clients scm-client-api - 1.52-SNAPSHOT + 1.52 @@ -70,7 +70,7 @@ sonia.scm scm-test - 1.52-SNAPSHOT + 1.52 test diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 9fba3adc5f..8393cdf7a2 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.52-SNAPSHOT + 1.52 sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 scm-core diff --git a/scm-dao-orientdb/pom.xml b/scm-dao-orientdb/pom.xml index 089818b33e..ec14afae25 100644 --- a/scm-dao-orientdb/pom.xml +++ b/scm-dao-orientdb/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.52-SNAPSHOT + 1.52 sonia.scm scm-dao-orientdb - 1.52-SNAPSHOT + 1.52 scm-dao-orientdb @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 @@ -52,7 +52,7 @@ sonia.scm scm-test - 1.52-SNAPSHOT + 1.52 test diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index 8b5843b34e..8ca90d299b 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.52-SNAPSHOT + 1.52 sonia.scm scm-dao-xml - 1.52-SNAPSHOT + 1.52 scm-dao-xml @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 @@ -34,7 +34,7 @@ sonia.scm scm-test - 1.52-SNAPSHOT + 1.52 test diff --git a/scm-plugin-backend/pom.xml b/scm-plugin-backend/pom.xml index d1a0f2129b..bce9e1c1ed 100644 --- a/scm-plugin-backend/pom.xml +++ b/scm-plugin-backend/pom.xml @@ -6,13 +6,13 @@ scm sonia.scm - 1.52-SNAPSHOT + 1.52 sonia.scm scm-plugin-backend war - 1.52-SNAPSHOT + 1.52 ${project.artifactId} @@ -62,7 +62,7 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 84f06ad33e..801b9e5afc 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52-SNAPSHOT + 1.52 sonia.scm.plugins scm-plugins pom - 1.52-SNAPSHOT + 1.52 scm-plugins @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 @@ -59,7 +59,7 @@ sonia.scm.maven scm-maven-plugin - 1.52-SNAPSHOT + 1.52 process-resources diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index e691c3ec96..cba220ff12 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.52-SNAPSHOT + 1.52 sonia.scm.plugins scm-git-plugin - 1.52-SNAPSHOT + 1.52 scm-git-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Git @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.52-SNAPSHOT + 1.52 test diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 0518bd4ef4..c802913576 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -6,12 +6,12 @@ sonia.scm.plugins scm-plugins - 1.52-SNAPSHOT + 1.52 sonia.scm.plugins scm-hg-plugin - 1.52-SNAPSHOT + 1.52 scm-hg-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Mercurial @@ -42,7 +42,7 @@ sonia.scm scm-test - 1.52-SNAPSHOT + 1.52 test diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 3afc620151..31e322d2a4 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.52-SNAPSHOT + 1.52 sonia.scm.plugins scm-svn-plugin - 1.52-SNAPSHOT + 1.52 scm-svn-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Subversion @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.52-SNAPSHOT + 1.52 test diff --git a/scm-samples/pom.xml b/scm-samples/pom.xml index e973485d63..f2e63250c4 100644 --- a/scm-samples/pom.xml +++ b/scm-samples/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52-SNAPSHOT + 1.52 sonia.scm.samples scm-samples pom - 1.52-SNAPSHOT + 1.52 scm-samples diff --git a/scm-samples/scm-sample-auth/pom.xml b/scm-samples/scm-sample-auth/pom.xml index c8b7589b36..9fc6db675d 100644 --- a/scm-samples/scm-sample-auth/pom.xml +++ b/scm-samples/scm-sample-auth/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.52-SNAPSHOT + 1.52 sonia.scm.sample scm-sample-auth - 1.52-SNAPSHOT + 1.52 scm-sample-auth Sample Authentication Plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 diff --git a/scm-samples/scm-sample-hello/pom.xml b/scm-samples/scm-sample-hello/pom.xml index a818f55e71..60cd02fead 100644 --- a/scm-samples/scm-sample-hello/pom.xml +++ b/scm-samples/scm-sample-hello/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.52-SNAPSHOT + 1.52 sonia.scm.sample scm-sample-hello - 1.52-SNAPSHOT + 1.52 scm-sample-hello A simple hello world plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 2ba693e1f3..dbab0c2a98 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.52-SNAPSHOT + 1.52 sonia.scm scm-server - 1.52-SNAPSHOT + 1.52 scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 34c3ec1426..55f396653f 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.52-SNAPSHOT + 1.52 sonia.scm scm-test - 1.52-SNAPSHOT + 1.52 scm-test @@ -25,7 +25,7 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index e1ee2a75ea..46f9b70bc6 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52-SNAPSHOT + 1.52 sonia.scm scm-webapp war - 1.52-SNAPSHOT + 1.52 scm-webapp @@ -38,31 +38,31 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 sonia.scm scm-dao-xml - 1.52-SNAPSHOT + 1.52 sonia.scm.plugins scm-hg-plugin - 1.52-SNAPSHOT + 1.52 sonia.scm.plugins scm-svn-plugin - 1.52-SNAPSHOT + 1.52 sonia.scm.plugins scm-git-plugin - 1.52-SNAPSHOT + 1.52 @@ -295,7 +295,7 @@ sonia.scm scm-test - 1.52-SNAPSHOT + 1.52 test @@ -308,7 +308,7 @@ sonia.scm.plugins scm-git-plugin - 1.52-SNAPSHOT + 1.52 tests test @@ -316,7 +316,7 @@ sonia.scm.plugins scm-hg-plugin - 1.52-SNAPSHOT + 1.52 tests test @@ -324,7 +324,7 @@ sonia.scm.plugins scm-svn-plugin - 1.52-SNAPSHOT + 1.52 tests test @@ -563,7 +563,7 @@ sonia.scm scm-dao-orientdb - 1.52-SNAPSHOT + 1.52 diff --git a/support/pom.xml b/support/pom.xml index 6086e5eb05..17d664a3cc 100644 --- a/support/pom.xml +++ b/support/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52-SNAPSHOT + 1.52 sonia.scm.support scm-support pom - 1.52-SNAPSHOT + 1.52 scm-support diff --git a/support/scm-support-btrace/pom.xml b/support/scm-support-btrace/pom.xml index aff17b047a..c691bf7438 100644 --- a/support/scm-support-btrace/pom.xml +++ b/support/scm-support-btrace/pom.xml @@ -4,12 +4,12 @@ sonia.scm.support scm-support - 1.52-SNAPSHOT + 1.52 sonia.scm scm-support-btrace - 1.52-SNAPSHOT + 1.52 jar scm-support-btrace @@ -18,7 +18,7 @@ sonia.scm scm-core - 1.52-SNAPSHOT + 1.52 From 14b2dd88e27a937d27aef5570a90a706e0cae381 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 10 May 2017 09:46:54 +0200 Subject: [PATCH 21/56] [maven-release-plugin] copy for tag 1.52 From 1d1e239d64d46f3dec2273bad86a6a2d1d2e3d1e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 10 May 2017 09:46:54 +0200 Subject: [PATCH 22/56] [maven-release-plugin] prepare for next development iteration --- maven/pom.xml | 4 ++-- maven/scm-maven-plugin/pom.xml | 4 ++-- maven/scm-plugin-archetype/pom.xml | 4 ++-- pom.xml | 4 ++-- scm-clients/pom.xml | 6 +++--- scm-clients/scm-cli-client/pom.xml | 6 +++--- scm-clients/scm-client-api/pom.xml | 4 ++-- scm-clients/scm-client-impl/pom.xml | 8 ++++---- scm-core/pom.xml | 4 ++-- scm-dao-orientdb/pom.xml | 8 ++++---- scm-dao-xml/pom.xml | 8 ++++---- scm-plugin-backend/pom.xml | 6 +++--- scm-plugins/pom.xml | 8 ++++---- scm-plugins/scm-git-plugin/pom.xml | 6 +++--- scm-plugins/scm-hg-plugin/pom.xml | 6 +++--- scm-plugins/scm-svn-plugin/pom.xml | 6 +++--- scm-samples/pom.xml | 4 ++-- scm-samples/scm-sample-auth/pom.xml | 6 +++--- scm-samples/scm-sample-hello/pom.xml | 6 +++--- scm-server/pom.xml | 4 ++-- scm-test/pom.xml | 6 +++--- scm-webapp/pom.xml | 24 ++++++++++++------------ support/pom.xml | 4 ++-- support/scm-support-btrace/pom.xml | 6 +++--- 24 files changed, 76 insertions(+), 76 deletions(-) diff --git a/maven/pom.xml b/maven/pom.xml index 1c857c89de..9cea727756 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52 + 1.53-SNAPSHOT sonia.scm.maven scm-maven-plugins pom - 1.52 + 1.53-SNAPSHOT scm-maven-plugins diff --git a/maven/scm-maven-plugin/pom.xml b/maven/scm-maven-plugin/pom.xml index 284ab3b4da..e5eb744e0e 100644 --- a/maven/scm-maven-plugin/pom.xml +++ b/maven/scm-maven-plugin/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.52 + 1.53-SNAPSHOT sonia.scm.maven scm-maven-plugin - 1.52 + 1.53-SNAPSHOT maven-plugin scm-maven-plugin diff --git a/maven/scm-plugin-archetype/pom.xml b/maven/scm-plugin-archetype/pom.xml index c92ed9d0b0..41c7d0a145 100644 --- a/maven/scm-plugin-archetype/pom.xml +++ b/maven/scm-plugin-archetype/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.52 + 1.53-SNAPSHOT sonia.scm.maven scm-plugin-archetype - 1.52 + 1.53-SNAPSHOT scm-plugin-archetype diff --git a/pom.xml b/pom.xml index 0ca87aef4f..86734d5078 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ sonia.scm scm pom - 1.52 + 1.53-SNAPSHOT The easiest way to share your Git, Mercurial and Subversion repositories over http. @@ -36,7 +36,7 @@ scm:hg:http://bitbucket.org/sdorra/scm-manager scm:hg:https://bitbucket.org/sdorra/scm-manager http://bitbucket.org/sdorra/scm-manager - 1.52 + HEAD diff --git a/scm-clients/pom.xml b/scm-clients/pom.xml index 0735a002b1..fabb86eeaa 100644 --- a/scm-clients/pom.xml +++ b/scm-clients/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52 + 1.53-SNAPSHOT sonia.scm.clients scm-clients pom - 1.52 + 1.53-SNAPSHOT scm-clients @@ -32,7 +32,7 @@ scm-core sonia.scm jar - 1.52 + 1.53-SNAPSHOT shiro-core diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml index 1ed1c9a8c9..badebe51c1 100644 --- a/scm-clients/scm-cli-client/pom.xml +++ b/scm-clients/scm-cli-client/pom.xml @@ -6,12 +6,12 @@ scm-clients sonia.scm.clients - 1.52 + 1.53-SNAPSHOT sonia.scm.clients scm-cli-client - 1.52 + 1.53-SNAPSHOT scm-cli-client @@ -34,7 +34,7 @@ sonia.scm.clients scm-client-impl - 1.52 + 1.53-SNAPSHOT diff --git a/scm-clients/scm-client-api/pom.xml b/scm-clients/scm-client-api/pom.xml index 4cf585a375..d1dcf4bee2 100644 --- a/scm-clients/scm-client-api/pom.xml +++ b/scm-clients/scm-client-api/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.52 + 1.53-SNAPSHOT sonia.scm.clients scm-client-api jar - 1.52 + 1.53-SNAPSHOT scm-client-api diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml index 1b1c6a7051..cd1d92369b 100644 --- a/scm-clients/scm-client-impl/pom.xml +++ b/scm-clients/scm-client-impl/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.52 + 1.53-SNAPSHOT sonia.scm.clients scm-client-impl jar - 1.52 + 1.53-SNAPSHOT scm-client-impl @@ -36,7 +36,7 @@ sonia.scm.clients scm-client-api - 1.52 + 1.53-SNAPSHOT @@ -70,7 +70,7 @@ sonia.scm scm-test - 1.52 + 1.53-SNAPSHOT test diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 8393cdf7a2..7c850be66d 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.52 + 1.53-SNAPSHOT sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT scm-core diff --git a/scm-dao-orientdb/pom.xml b/scm-dao-orientdb/pom.xml index ec14afae25..36bc1ab40a 100644 --- a/scm-dao-orientdb/pom.xml +++ b/scm-dao-orientdb/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.52 + 1.53-SNAPSHOT sonia.scm scm-dao-orientdb - 1.52 + 1.53-SNAPSHOT scm-dao-orientdb @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT @@ -52,7 +52,7 @@ sonia.scm scm-test - 1.52 + 1.53-SNAPSHOT test diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index 8ca90d299b..1991b366d5 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.52 + 1.53-SNAPSHOT sonia.scm scm-dao-xml - 1.52 + 1.53-SNAPSHOT scm-dao-xml @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT @@ -34,7 +34,7 @@ sonia.scm scm-test - 1.52 + 1.53-SNAPSHOT test diff --git a/scm-plugin-backend/pom.xml b/scm-plugin-backend/pom.xml index bce9e1c1ed..e104778b80 100644 --- a/scm-plugin-backend/pom.xml +++ b/scm-plugin-backend/pom.xml @@ -6,13 +6,13 @@ scm sonia.scm - 1.52 + 1.53-SNAPSHOT sonia.scm scm-plugin-backend war - 1.52 + 1.53-SNAPSHOT ${project.artifactId} @@ -62,7 +62,7 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 801b9e5afc..bcc38a33e0 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52 + 1.53-SNAPSHOT sonia.scm.plugins scm-plugins pom - 1.52 + 1.53-SNAPSHOT scm-plugins @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT @@ -59,7 +59,7 @@ sonia.scm.maven scm-maven-plugin - 1.52 + 1.53-SNAPSHOT process-resources diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index cba220ff12..2d46e9f447 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.52 + 1.53-SNAPSHOT sonia.scm.plugins scm-git-plugin - 1.52 + 1.53-SNAPSHOT scm-git-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Git @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.52 + 1.53-SNAPSHOT test diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index c802913576..ba0d1a3f50 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -6,12 +6,12 @@ sonia.scm.plugins scm-plugins - 1.52 + 1.53-SNAPSHOT sonia.scm.plugins scm-hg-plugin - 1.52 + 1.53-SNAPSHOT scm-hg-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Mercurial @@ -42,7 +42,7 @@ sonia.scm scm-test - 1.52 + 1.53-SNAPSHOT test diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 31e322d2a4..7cbf8376d2 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.52 + 1.53-SNAPSHOT sonia.scm.plugins scm-svn-plugin - 1.52 + 1.53-SNAPSHOT scm-svn-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Subversion @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.52 + 1.53-SNAPSHOT test diff --git a/scm-samples/pom.xml b/scm-samples/pom.xml index f2e63250c4..643e4e5c02 100644 --- a/scm-samples/pom.xml +++ b/scm-samples/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52 + 1.53-SNAPSHOT sonia.scm.samples scm-samples pom - 1.52 + 1.53-SNAPSHOT scm-samples diff --git a/scm-samples/scm-sample-auth/pom.xml b/scm-samples/scm-sample-auth/pom.xml index 9fc6db675d..0c3f189082 100644 --- a/scm-samples/scm-sample-auth/pom.xml +++ b/scm-samples/scm-sample-auth/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.52 + 1.53-SNAPSHOT sonia.scm.sample scm-sample-auth - 1.52 + 1.53-SNAPSHOT scm-sample-auth Sample Authentication Plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT diff --git a/scm-samples/scm-sample-hello/pom.xml b/scm-samples/scm-sample-hello/pom.xml index 60cd02fead..1f4ac39b99 100644 --- a/scm-samples/scm-sample-hello/pom.xml +++ b/scm-samples/scm-sample-hello/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.52 + 1.53-SNAPSHOT sonia.scm.sample scm-sample-hello - 1.52 + 1.53-SNAPSHOT scm-sample-hello A simple hello world plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT diff --git a/scm-server/pom.xml b/scm-server/pom.xml index dbab0c2a98..4b9263a2e8 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.52 + 1.53-SNAPSHOT sonia.scm scm-server - 1.52 + 1.53-SNAPSHOT scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 55f396653f..aac44541c6 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.52 + 1.53-SNAPSHOT sonia.scm scm-test - 1.52 + 1.53-SNAPSHOT scm-test @@ -25,7 +25,7 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 46f9b70bc6..29ef4c7c76 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52 + 1.53-SNAPSHOT sonia.scm scm-webapp war - 1.52 + 1.53-SNAPSHOT scm-webapp @@ -38,31 +38,31 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT sonia.scm scm-dao-xml - 1.52 + 1.53-SNAPSHOT sonia.scm.plugins scm-hg-plugin - 1.52 + 1.53-SNAPSHOT sonia.scm.plugins scm-svn-plugin - 1.52 + 1.53-SNAPSHOT sonia.scm.plugins scm-git-plugin - 1.52 + 1.53-SNAPSHOT @@ -295,7 +295,7 @@ sonia.scm scm-test - 1.52 + 1.53-SNAPSHOT test @@ -308,7 +308,7 @@ sonia.scm.plugins scm-git-plugin - 1.52 + 1.53-SNAPSHOT tests test @@ -316,7 +316,7 @@ sonia.scm.plugins scm-hg-plugin - 1.52 + 1.53-SNAPSHOT tests test @@ -324,7 +324,7 @@ sonia.scm.plugins scm-svn-plugin - 1.52 + 1.53-SNAPSHOT tests test @@ -563,7 +563,7 @@ sonia.scm scm-dao-orientdb - 1.52 + 1.53-SNAPSHOT diff --git a/support/pom.xml b/support/pom.xml index 17d664a3cc..96e1689c4e 100644 --- a/support/pom.xml +++ b/support/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.52 + 1.53-SNAPSHOT sonia.scm.support scm-support pom - 1.52 + 1.53-SNAPSHOT scm-support diff --git a/support/scm-support-btrace/pom.xml b/support/scm-support-btrace/pom.xml index c691bf7438..46873aa444 100644 --- a/support/scm-support-btrace/pom.xml +++ b/support/scm-support-btrace/pom.xml @@ -4,12 +4,12 @@ sonia.scm.support scm-support - 1.52 + 1.53-SNAPSHOT sonia.scm scm-support-btrace - 1.52 + 1.53-SNAPSHOT jar scm-support-btrace @@ -18,7 +18,7 @@ sonia.scm scm-core - 1.52 + 1.53-SNAPSHOT From 71308a9c8a30d81b543c126d8805ed228d28cf7e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 14 May 2017 10:12:29 +0200 Subject: [PATCH 23/56] fix jax-rs classpath conflict, see #916 --- scm-webapp/pom.xml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 29ef4c7c76..4f139e6a9b 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -276,18 +276,8 @@ com.webcohesion.enunciate - enunciate-jaxrs + enunciate-core-annotations ${enunciate.version} - - - jackson-jaxrs - org.codehaus.jackson - - - jackson-xc - org.codehaus.jackson - - From 72cdd1ccd2fe1e7aa2274e07172e338841b3ae42 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 15 May 2017 21:41:57 +0200 Subject: [PATCH 24/56] close branch issue-916 From ac07564085c9ba75642450ec2d3cbab8f34ee8bd Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 15 May 2017 22:04:23 +0200 Subject: [PATCH 25/56] update nativepkg-maven-plugin to version 1.1.4 --- scm-server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 4b9263a2e8..24949082a1 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -178,7 +178,7 @@ com.github.sdorra nativepkg-maven-plugin - 1.1.3 + 1.1.4 From 95bc7607c4582b6e72b0db9a03a3c35dfd1ef05d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 May 2017 08:36:54 +0200 Subject: [PATCH 26/56] [maven-release-plugin] prepare release 1.53 --- maven/pom.xml | 4 ++-- maven/scm-maven-plugin/pom.xml | 4 ++-- maven/scm-plugin-archetype/pom.xml | 4 ++-- pom.xml | 4 ++-- scm-clients/pom.xml | 6 +++--- scm-clients/scm-cli-client/pom.xml | 6 +++--- scm-clients/scm-client-api/pom.xml | 4 ++-- scm-clients/scm-client-impl/pom.xml | 8 ++++---- scm-core/pom.xml | 4 ++-- scm-dao-orientdb/pom.xml | 8 ++++---- scm-dao-xml/pom.xml | 8 ++++---- scm-plugin-backend/pom.xml | 6 +++--- scm-plugins/pom.xml | 8 ++++---- scm-plugins/scm-git-plugin/pom.xml | 6 +++--- scm-plugins/scm-hg-plugin/pom.xml | 6 +++--- scm-plugins/scm-svn-plugin/pom.xml | 6 +++--- scm-samples/pom.xml | 4 ++-- scm-samples/scm-sample-auth/pom.xml | 6 +++--- scm-samples/scm-sample-hello/pom.xml | 6 +++--- scm-server/pom.xml | 4 ++-- scm-test/pom.xml | 6 +++--- scm-webapp/pom.xml | 24 ++++++++++++------------ support/pom.xml | 4 ++-- support/scm-support-btrace/pom.xml | 6 +++--- 24 files changed, 76 insertions(+), 76 deletions(-) diff --git a/maven/pom.xml b/maven/pom.xml index 9cea727756..0839edfea0 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53-SNAPSHOT + 1.53 sonia.scm.maven scm-maven-plugins pom - 1.53-SNAPSHOT + 1.53 scm-maven-plugins diff --git a/maven/scm-maven-plugin/pom.xml b/maven/scm-maven-plugin/pom.xml index e5eb744e0e..5f684a13ea 100644 --- a/maven/scm-maven-plugin/pom.xml +++ b/maven/scm-maven-plugin/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.53-SNAPSHOT + 1.53 sonia.scm.maven scm-maven-plugin - 1.53-SNAPSHOT + 1.53 maven-plugin scm-maven-plugin diff --git a/maven/scm-plugin-archetype/pom.xml b/maven/scm-plugin-archetype/pom.xml index 41c7d0a145..c00d58dac9 100644 --- a/maven/scm-plugin-archetype/pom.xml +++ b/maven/scm-plugin-archetype/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.53-SNAPSHOT + 1.53 sonia.scm.maven scm-plugin-archetype - 1.53-SNAPSHOT + 1.53 scm-plugin-archetype diff --git a/pom.xml b/pom.xml index 86734d5078..c56dee15fe 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ sonia.scm scm pom - 1.53-SNAPSHOT + 1.53 The easiest way to share your Git, Mercurial and Subversion repositories over http. @@ -36,7 +36,7 @@ scm:hg:http://bitbucket.org/sdorra/scm-manager scm:hg:https://bitbucket.org/sdorra/scm-manager http://bitbucket.org/sdorra/scm-manager - HEAD + 1.53 diff --git a/scm-clients/pom.xml b/scm-clients/pom.xml index fabb86eeaa..01b338b68d 100644 --- a/scm-clients/pom.xml +++ b/scm-clients/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53-SNAPSHOT + 1.53 sonia.scm.clients scm-clients pom - 1.53-SNAPSHOT + 1.53 scm-clients @@ -32,7 +32,7 @@ scm-core sonia.scm jar - 1.53-SNAPSHOT + 1.53 shiro-core diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml index badebe51c1..3cd14571d5 100644 --- a/scm-clients/scm-cli-client/pom.xml +++ b/scm-clients/scm-cli-client/pom.xml @@ -6,12 +6,12 @@ scm-clients sonia.scm.clients - 1.53-SNAPSHOT + 1.53 sonia.scm.clients scm-cli-client - 1.53-SNAPSHOT + 1.53 scm-cli-client @@ -34,7 +34,7 @@ sonia.scm.clients scm-client-impl - 1.53-SNAPSHOT + 1.53 diff --git a/scm-clients/scm-client-api/pom.xml b/scm-clients/scm-client-api/pom.xml index d1dcf4bee2..9a3c9bc523 100644 --- a/scm-clients/scm-client-api/pom.xml +++ b/scm-clients/scm-client-api/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.53-SNAPSHOT + 1.53 sonia.scm.clients scm-client-api jar - 1.53-SNAPSHOT + 1.53 scm-client-api diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml index cd1d92369b..2b5666fe4b 100644 --- a/scm-clients/scm-client-impl/pom.xml +++ b/scm-clients/scm-client-impl/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.53-SNAPSHOT + 1.53 sonia.scm.clients scm-client-impl jar - 1.53-SNAPSHOT + 1.53 scm-client-impl @@ -36,7 +36,7 @@ sonia.scm.clients scm-client-api - 1.53-SNAPSHOT + 1.53 @@ -70,7 +70,7 @@ sonia.scm scm-test - 1.53-SNAPSHOT + 1.53 test diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 7c850be66d..936f8abac3 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.53-SNAPSHOT + 1.53 sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 scm-core diff --git a/scm-dao-orientdb/pom.xml b/scm-dao-orientdb/pom.xml index 36bc1ab40a..6e8f7e3fb1 100644 --- a/scm-dao-orientdb/pom.xml +++ b/scm-dao-orientdb/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.53-SNAPSHOT + 1.53 sonia.scm scm-dao-orientdb - 1.53-SNAPSHOT + 1.53 scm-dao-orientdb @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 @@ -52,7 +52,7 @@ sonia.scm scm-test - 1.53-SNAPSHOT + 1.53 test diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index 1991b366d5..109efed3aa 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.53-SNAPSHOT + 1.53 sonia.scm scm-dao-xml - 1.53-SNAPSHOT + 1.53 scm-dao-xml @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 @@ -34,7 +34,7 @@ sonia.scm scm-test - 1.53-SNAPSHOT + 1.53 test diff --git a/scm-plugin-backend/pom.xml b/scm-plugin-backend/pom.xml index e104778b80..8ab85e3d86 100644 --- a/scm-plugin-backend/pom.xml +++ b/scm-plugin-backend/pom.xml @@ -6,13 +6,13 @@ scm sonia.scm - 1.53-SNAPSHOT + 1.53 sonia.scm scm-plugin-backend war - 1.53-SNAPSHOT + 1.53 ${project.artifactId} @@ -62,7 +62,7 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index bcc38a33e0..26b1d0218d 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53-SNAPSHOT + 1.53 sonia.scm.plugins scm-plugins pom - 1.53-SNAPSHOT + 1.53 scm-plugins @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 @@ -59,7 +59,7 @@ sonia.scm.maven scm-maven-plugin - 1.53-SNAPSHOT + 1.53 process-resources diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 2d46e9f447..3b74c2c2f6 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.53-SNAPSHOT + 1.53 sonia.scm.plugins scm-git-plugin - 1.53-SNAPSHOT + 1.53 scm-git-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Git @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.53-SNAPSHOT + 1.53 test diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index ba0d1a3f50..5c3b6ee62d 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -6,12 +6,12 @@ sonia.scm.plugins scm-plugins - 1.53-SNAPSHOT + 1.53 sonia.scm.plugins scm-hg-plugin - 1.53-SNAPSHOT + 1.53 scm-hg-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Mercurial @@ -42,7 +42,7 @@ sonia.scm scm-test - 1.53-SNAPSHOT + 1.53 test diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 7cbf8376d2..c1b0be7970 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.53-SNAPSHOT + 1.53 sonia.scm.plugins scm-svn-plugin - 1.53-SNAPSHOT + 1.53 scm-svn-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Subversion @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.53-SNAPSHOT + 1.53 test diff --git a/scm-samples/pom.xml b/scm-samples/pom.xml index 643e4e5c02..6c87875590 100644 --- a/scm-samples/pom.xml +++ b/scm-samples/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53-SNAPSHOT + 1.53 sonia.scm.samples scm-samples pom - 1.53-SNAPSHOT + 1.53 scm-samples diff --git a/scm-samples/scm-sample-auth/pom.xml b/scm-samples/scm-sample-auth/pom.xml index 0c3f189082..1de35af9b3 100644 --- a/scm-samples/scm-sample-auth/pom.xml +++ b/scm-samples/scm-sample-auth/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.53-SNAPSHOT + 1.53 sonia.scm.sample scm-sample-auth - 1.53-SNAPSHOT + 1.53 scm-sample-auth Sample Authentication Plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 diff --git a/scm-samples/scm-sample-hello/pom.xml b/scm-samples/scm-sample-hello/pom.xml index 1f4ac39b99..a55a25a72e 100644 --- a/scm-samples/scm-sample-hello/pom.xml +++ b/scm-samples/scm-sample-hello/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.53-SNAPSHOT + 1.53 sonia.scm.sample scm-sample-hello - 1.53-SNAPSHOT + 1.53 scm-sample-hello A simple hello world plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 24949082a1..51a76e4b64 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.53-SNAPSHOT + 1.53 sonia.scm scm-server - 1.53-SNAPSHOT + 1.53 scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index aac44541c6..70ab8014cf 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.53-SNAPSHOT + 1.53 sonia.scm scm-test - 1.53-SNAPSHOT + 1.53 scm-test @@ -25,7 +25,7 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 4f139e6a9b..3f22d522c0 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53-SNAPSHOT + 1.53 sonia.scm scm-webapp war - 1.53-SNAPSHOT + 1.53 scm-webapp @@ -38,31 +38,31 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 sonia.scm scm-dao-xml - 1.53-SNAPSHOT + 1.53 sonia.scm.plugins scm-hg-plugin - 1.53-SNAPSHOT + 1.53 sonia.scm.plugins scm-svn-plugin - 1.53-SNAPSHOT + 1.53 sonia.scm.plugins scm-git-plugin - 1.53-SNAPSHOT + 1.53 @@ -285,7 +285,7 @@ sonia.scm scm-test - 1.53-SNAPSHOT + 1.53 test @@ -298,7 +298,7 @@ sonia.scm.plugins scm-git-plugin - 1.53-SNAPSHOT + 1.53 tests test @@ -306,7 +306,7 @@ sonia.scm.plugins scm-hg-plugin - 1.53-SNAPSHOT + 1.53 tests test @@ -314,7 +314,7 @@ sonia.scm.plugins scm-svn-plugin - 1.53-SNAPSHOT + 1.53 tests test @@ -553,7 +553,7 @@ sonia.scm scm-dao-orientdb - 1.53-SNAPSHOT + 1.53 diff --git a/support/pom.xml b/support/pom.xml index 96e1689c4e..745a89ea0a 100644 --- a/support/pom.xml +++ b/support/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53-SNAPSHOT + 1.53 sonia.scm.support scm-support pom - 1.53-SNAPSHOT + 1.53 scm-support diff --git a/support/scm-support-btrace/pom.xml b/support/scm-support-btrace/pom.xml index 46873aa444..e35ce3ae29 100644 --- a/support/scm-support-btrace/pom.xml +++ b/support/scm-support-btrace/pom.xml @@ -4,12 +4,12 @@ sonia.scm.support scm-support - 1.53-SNAPSHOT + 1.53 sonia.scm scm-support-btrace - 1.53-SNAPSHOT + 1.53 jar scm-support-btrace @@ -18,7 +18,7 @@ sonia.scm scm-core - 1.53-SNAPSHOT + 1.53 From 8d7b2556eb9075bff0174f611fbb348dcfe33d3d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 May 2017 08:36:54 +0200 Subject: [PATCH 27/56] [maven-release-plugin] copy for tag 1.53 From 5cae7ab35a154221d4fb947e9cfc25ed4af4411e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 17 May 2017 08:36:55 +0200 Subject: [PATCH 28/56] [maven-release-plugin] prepare for next development iteration --- maven/pom.xml | 4 ++-- maven/scm-maven-plugin/pom.xml | 4 ++-- maven/scm-plugin-archetype/pom.xml | 4 ++-- pom.xml | 4 ++-- scm-clients/pom.xml | 6 +++--- scm-clients/scm-cli-client/pom.xml | 6 +++--- scm-clients/scm-client-api/pom.xml | 4 ++-- scm-clients/scm-client-impl/pom.xml | 8 ++++---- scm-core/pom.xml | 4 ++-- scm-dao-orientdb/pom.xml | 8 ++++---- scm-dao-xml/pom.xml | 8 ++++---- scm-plugin-backend/pom.xml | 6 +++--- scm-plugins/pom.xml | 8 ++++---- scm-plugins/scm-git-plugin/pom.xml | 6 +++--- scm-plugins/scm-hg-plugin/pom.xml | 6 +++--- scm-plugins/scm-svn-plugin/pom.xml | 6 +++--- scm-samples/pom.xml | 4 ++-- scm-samples/scm-sample-auth/pom.xml | 6 +++--- scm-samples/scm-sample-hello/pom.xml | 6 +++--- scm-server/pom.xml | 4 ++-- scm-test/pom.xml | 6 +++--- scm-webapp/pom.xml | 24 ++++++++++++------------ support/pom.xml | 4 ++-- support/scm-support-btrace/pom.xml | 6 +++--- 24 files changed, 76 insertions(+), 76 deletions(-) diff --git a/maven/pom.xml b/maven/pom.xml index 0839edfea0..d928f0515c 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53 + 1.54-SNAPSHOT sonia.scm.maven scm-maven-plugins pom - 1.53 + 1.54-SNAPSHOT scm-maven-plugins diff --git a/maven/scm-maven-plugin/pom.xml b/maven/scm-maven-plugin/pom.xml index 5f684a13ea..f038c904ab 100644 --- a/maven/scm-maven-plugin/pom.xml +++ b/maven/scm-maven-plugin/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.53 + 1.54-SNAPSHOT sonia.scm.maven scm-maven-plugin - 1.53 + 1.54-SNAPSHOT maven-plugin scm-maven-plugin diff --git a/maven/scm-plugin-archetype/pom.xml b/maven/scm-plugin-archetype/pom.xml index c00d58dac9..9aec6b5ac3 100644 --- a/maven/scm-plugin-archetype/pom.xml +++ b/maven/scm-plugin-archetype/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.53 + 1.54-SNAPSHOT sonia.scm.maven scm-plugin-archetype - 1.53 + 1.54-SNAPSHOT scm-plugin-archetype diff --git a/pom.xml b/pom.xml index c56dee15fe..4d256d8e1e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ sonia.scm scm pom - 1.53 + 1.54-SNAPSHOT The easiest way to share your Git, Mercurial and Subversion repositories over http. @@ -36,7 +36,7 @@ scm:hg:http://bitbucket.org/sdorra/scm-manager scm:hg:https://bitbucket.org/sdorra/scm-manager http://bitbucket.org/sdorra/scm-manager - 1.53 + HEAD diff --git a/scm-clients/pom.xml b/scm-clients/pom.xml index 01b338b68d..dc6b8015c7 100644 --- a/scm-clients/pom.xml +++ b/scm-clients/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53 + 1.54-SNAPSHOT sonia.scm.clients scm-clients pom - 1.53 + 1.54-SNAPSHOT scm-clients @@ -32,7 +32,7 @@ scm-core sonia.scm jar - 1.53 + 1.54-SNAPSHOT shiro-core diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml index 3cd14571d5..340d3c0260 100644 --- a/scm-clients/scm-cli-client/pom.xml +++ b/scm-clients/scm-cli-client/pom.xml @@ -6,12 +6,12 @@ scm-clients sonia.scm.clients - 1.53 + 1.54-SNAPSHOT sonia.scm.clients scm-cli-client - 1.53 + 1.54-SNAPSHOT scm-cli-client @@ -34,7 +34,7 @@ sonia.scm.clients scm-client-impl - 1.53 + 1.54-SNAPSHOT diff --git a/scm-clients/scm-client-api/pom.xml b/scm-clients/scm-client-api/pom.xml index 9a3c9bc523..0e31381fc5 100644 --- a/scm-clients/scm-client-api/pom.xml +++ b/scm-clients/scm-client-api/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.53 + 1.54-SNAPSHOT sonia.scm.clients scm-client-api jar - 1.53 + 1.54-SNAPSHOT scm-client-api diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml index 2b5666fe4b..414cc661a7 100644 --- a/scm-clients/scm-client-impl/pom.xml +++ b/scm-clients/scm-client-impl/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.53 + 1.54-SNAPSHOT sonia.scm.clients scm-client-impl jar - 1.53 + 1.54-SNAPSHOT scm-client-impl @@ -36,7 +36,7 @@ sonia.scm.clients scm-client-api - 1.53 + 1.54-SNAPSHOT @@ -70,7 +70,7 @@ sonia.scm scm-test - 1.53 + 1.54-SNAPSHOT test diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 936f8abac3..59f89d6f4a 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.53 + 1.54-SNAPSHOT sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT scm-core diff --git a/scm-dao-orientdb/pom.xml b/scm-dao-orientdb/pom.xml index 6e8f7e3fb1..3af411018f 100644 --- a/scm-dao-orientdb/pom.xml +++ b/scm-dao-orientdb/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.53 + 1.54-SNAPSHOT sonia.scm scm-dao-orientdb - 1.53 + 1.54-SNAPSHOT scm-dao-orientdb @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT @@ -52,7 +52,7 @@ sonia.scm scm-test - 1.53 + 1.54-SNAPSHOT test diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index 109efed3aa..2732e02890 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.53 + 1.54-SNAPSHOT sonia.scm scm-dao-xml - 1.53 + 1.54-SNAPSHOT scm-dao-xml @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT @@ -34,7 +34,7 @@ sonia.scm scm-test - 1.53 + 1.54-SNAPSHOT test diff --git a/scm-plugin-backend/pom.xml b/scm-plugin-backend/pom.xml index 8ab85e3d86..cd37506db1 100644 --- a/scm-plugin-backend/pom.xml +++ b/scm-plugin-backend/pom.xml @@ -6,13 +6,13 @@ scm sonia.scm - 1.53 + 1.54-SNAPSHOT sonia.scm scm-plugin-backend war - 1.53 + 1.54-SNAPSHOT ${project.artifactId} @@ -62,7 +62,7 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 26b1d0218d..f89e1c5512 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53 + 1.54-SNAPSHOT sonia.scm.plugins scm-plugins pom - 1.53 + 1.54-SNAPSHOT scm-plugins @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT @@ -59,7 +59,7 @@ sonia.scm.maven scm-maven-plugin - 1.53 + 1.54-SNAPSHOT process-resources diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 3b74c2c2f6..5ab04d1e1c 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.53 + 1.54-SNAPSHOT sonia.scm.plugins scm-git-plugin - 1.53 + 1.54-SNAPSHOT scm-git-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Git @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.53 + 1.54-SNAPSHOT test diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 5c3b6ee62d..170fb089ed 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -6,12 +6,12 @@ sonia.scm.plugins scm-plugins - 1.53 + 1.54-SNAPSHOT sonia.scm.plugins scm-hg-plugin - 1.53 + 1.54-SNAPSHOT scm-hg-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Mercurial @@ -42,7 +42,7 @@ sonia.scm scm-test - 1.53 + 1.54-SNAPSHOT test diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index c1b0be7970..cffc66d9b2 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.53 + 1.54-SNAPSHOT sonia.scm.plugins scm-svn-plugin - 1.53 + 1.54-SNAPSHOT scm-svn-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Subversion @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.53 + 1.54-SNAPSHOT test diff --git a/scm-samples/pom.xml b/scm-samples/pom.xml index 6c87875590..5000e2e762 100644 --- a/scm-samples/pom.xml +++ b/scm-samples/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53 + 1.54-SNAPSHOT sonia.scm.samples scm-samples pom - 1.53 + 1.54-SNAPSHOT scm-samples diff --git a/scm-samples/scm-sample-auth/pom.xml b/scm-samples/scm-sample-auth/pom.xml index 1de35af9b3..3d95159d88 100644 --- a/scm-samples/scm-sample-auth/pom.xml +++ b/scm-samples/scm-sample-auth/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.53 + 1.54-SNAPSHOT sonia.scm.sample scm-sample-auth - 1.53 + 1.54-SNAPSHOT scm-sample-auth Sample Authentication Plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT diff --git a/scm-samples/scm-sample-hello/pom.xml b/scm-samples/scm-sample-hello/pom.xml index a55a25a72e..ca98c15641 100644 --- a/scm-samples/scm-sample-hello/pom.xml +++ b/scm-samples/scm-sample-hello/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.53 + 1.54-SNAPSHOT sonia.scm.sample scm-sample-hello - 1.53 + 1.54-SNAPSHOT scm-sample-hello A simple hello world plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 51a76e4b64..0ce9c9d923 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.53 + 1.54-SNAPSHOT sonia.scm scm-server - 1.53 + 1.54-SNAPSHOT scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 70ab8014cf..a7595788ec 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.53 + 1.54-SNAPSHOT sonia.scm scm-test - 1.53 + 1.54-SNAPSHOT scm-test @@ -25,7 +25,7 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 3f22d522c0..2ea9bbca4b 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53 + 1.54-SNAPSHOT sonia.scm scm-webapp war - 1.53 + 1.54-SNAPSHOT scm-webapp @@ -38,31 +38,31 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT sonia.scm scm-dao-xml - 1.53 + 1.54-SNAPSHOT sonia.scm.plugins scm-hg-plugin - 1.53 + 1.54-SNAPSHOT sonia.scm.plugins scm-svn-plugin - 1.53 + 1.54-SNAPSHOT sonia.scm.plugins scm-git-plugin - 1.53 + 1.54-SNAPSHOT @@ -285,7 +285,7 @@ sonia.scm scm-test - 1.53 + 1.54-SNAPSHOT test @@ -298,7 +298,7 @@ sonia.scm.plugins scm-git-plugin - 1.53 + 1.54-SNAPSHOT tests test @@ -306,7 +306,7 @@ sonia.scm.plugins scm-hg-plugin - 1.53 + 1.54-SNAPSHOT tests test @@ -314,7 +314,7 @@ sonia.scm.plugins scm-svn-plugin - 1.53 + 1.54-SNAPSHOT tests test @@ -553,7 +553,7 @@ sonia.scm scm-dao-orientdb - 1.53 + 1.54-SNAPSHOT diff --git a/support/pom.xml b/support/pom.xml index 745a89ea0a..091bded8a7 100644 --- a/support/pom.xml +++ b/support/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.53 + 1.54-SNAPSHOT sonia.scm.support scm-support pom - 1.53 + 1.54-SNAPSHOT scm-support diff --git a/support/scm-support-btrace/pom.xml b/support/scm-support-btrace/pom.xml index e35ce3ae29..698824670d 100644 --- a/support/scm-support-btrace/pom.xml +++ b/support/scm-support-btrace/pom.xml @@ -4,12 +4,12 @@ sonia.scm.support scm-support - 1.53 + 1.54-SNAPSHOT sonia.scm scm-support-btrace - 1.53 + 1.54-SNAPSHOT jar scm-support-btrace @@ -18,7 +18,7 @@ sonia.scm scm-core - 1.53 + 1.54-SNAPSHOT From 80fe417206fa669bf3db80f05fbca7f02735c731 Mon Sep 17 00:00:00 2001 From: Oliver Milke Date: Wed, 10 May 2017 13:56:13 +0200 Subject: [PATCH 29/56] fixing test execution on german / windows machines --- .../test/java/sonia/scm/i18n/I18nMessagesTest.java | 11 +++++++++++ .../src/main/java/sonia/scm/AbstractTestBase.java | 8 +++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/scm-core/src/test/java/sonia/scm/i18n/I18nMessagesTest.java b/scm-core/src/test/java/sonia/scm/i18n/I18nMessagesTest.java index 0760c4efb6..1e317ff62e 100644 --- a/scm-core/src/test/java/sonia/scm/i18n/I18nMessagesTest.java +++ b/scm-core/src/test/java/sonia/scm/i18n/I18nMessagesTest.java @@ -58,6 +58,17 @@ public class I18nMessagesTest @Test public void testI18n() { + /* + lookup-order for this test: + - TM_en (es specified, but not ava) + - TM_ + - TM + + This means that, if there is no default locale specified, this test accidentally passes on non-german machines, an fails on german machines, since the execution locale is de_DE, which is checked even before the fallback locale is considered. + */ + + Locale.setDefault(Locale.ENGLISH); + TestMessages msg = I18nMessages.get(TestMessages.class); assertEquals("Normal Key", msg.normalKey); diff --git a/scm-test/src/main/java/sonia/scm/AbstractTestBase.java b/scm-test/src/main/java/sonia/scm/AbstractTestBase.java index 47ee59b075..13cde0391e 100644 --- a/scm-test/src/main/java/sonia/scm/AbstractTestBase.java +++ b/scm-test/src/main/java/sonia/scm/AbstractTestBase.java @@ -55,7 +55,9 @@ import static org.junit.Assert.*; import java.io.File; +import java.io.IOException; import java.util.UUID; +import java.util.logging.Logger; /** * @@ -155,7 +157,11 @@ public class AbstractTestBase } finally { - IOUtil.delete(tempDirectory); + try { + IOUtil.delete(tempDirectory); + } catch (IOException e) { + Logger.getGlobal().warning(String.format("deleting temp <%s> failed: %s", tempDirectory.getAbsolutePath(), e.getMessage())); + } } } From 9b932a325ecc1d40228298258d66756b9f2fbff7 Mon Sep 17 00:00:00 2001 From: Oliver Milke Date: Fri, 19 May 2017 17:27:48 +0200 Subject: [PATCH 30/56] refactor git repository matching for accepting optional .git suffix --- .../scm/repository/GitRepositoryHandler.java | 5 +- .../repository/DefaultRepositoryManager.java | 48 ++++++++++++++----- .../DefaultRepositoryManagerTest.java | 24 ++++++++++ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java index 85704a611a..d806b5526f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java @@ -82,7 +82,10 @@ public class GitRepositoryHandler /** Field description */ public static final String TYPE_NAME = "git"; - + + + public static final String DOT_GIT = ".git"; + private static final Logger logger = LoggerFactory.getLogger(GitRepositoryHandler.class); /** Field description */ 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 bbe4f80115..5369ee1c11 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -35,6 +35,7 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -972,24 +973,45 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager } /** - * Method description + * This method checks whether or not the provided path belongs to the provided repository. * - * - * @param repository - * @param path - * - * @return + * @param repository The repository to be tested. + * @param path The path that might be part of the repository. + * @return Returns true if path belongs to the repository. Returns false otherwise. */ - private boolean isNameMatching(Repository repository, String path) - { + private boolean isNameMatching(Repository repository, String path) { + return isNameMatching(repository.getType(), repository.getName(), path); + } + + /** + * This method checks whether or not the provided path belongs to the provided repository. + * + * @param repositoryType The type of the repository being tested. + * @param repositoryName The name of the repository being tested. + * @param path The path that might be part of the repository. + * @return Returns true if path belongs to the repository. Returns false otherwise. + */ + @VisibleForTesting + boolean isNameMatching(String repositoryType, String repositoryName, String path) { boolean result = false; - String name = repository.getName(); - if (path.startsWith(name)) - { - String sub = path.substring(name.length()); + if (path.startsWith(repositoryName)) { + + String pathPart = path.substring(repositoryName.length()); + + //TODO: this introduces a strong coupling to the git plugin. This can be resolved with a "Repository Matcher" API. + //ausformulieren, ticketId weg + if (GitRepositoryHandler.TYPE_NAME.equals(repositoryType)) { + + //git repository may also be named <>.git by convention + if (pathPart.startsWith(GitRepositoryHandler.DOT_GIT)) { + //if this is the case, just also cut it away + pathPart = pathPart.substring(GitRepositoryHandler.DOT_GIT.length()); + } + } + + result = Util.isEmpty(pathPart) || pathPart.startsWith(HttpUtil.SEPARATOR_PATH); - result = Util.isEmpty(sub) || sub.startsWith(HttpUtil.SEPARATOR_PATH); } return result; 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 28fa3f862c..7b7c304230 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -46,6 +46,7 @@ import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.store.JAXBStoreFactory; import sonia.scm.store.StoreFactory; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -92,6 +93,29 @@ public class DefaultRepositoryManagerTest extends RepositoryManagerTestBase assertNull(m.getFromUri("/git/project1/test-3/ka/some/path")); } + @Test + public void testNameIsMatching() throws Exception { + DefaultRepositoryManager m = createManager(); + + assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-name"), is(true)); + assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-name/"), is(true)); + assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-name/and-more-is-valid"), is(true)); + assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-name.git/and-more-is-valid"), + is(true)); + + + assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "not-the-name"), is(false)); + assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-na"), is(false)); + + assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "/repo-name/"), is(false)); + + assertThat(m.isNameMatching(HgRepositoryHandler.TYPE_NAME, "repo-name", "repo-name.git/and-more-is-valid"), + is(false)); + assertThat(m.isNameMatching(SvnRepositoryHandler.TYPE_NAME, "repo-name", "repo-name.git/and-more-is-valid"), + is(false)); + + } + //~--- methods -------------------------------------------------------------- /** From 33ea2273b004f1f276c82e27e68951a68aaf8d18 Mon Sep 17 00:00:00 2001 From: Oliver Milke Date: Fri, 19 May 2017 17:27:18 +0200 Subject: [PATCH 31/56] Add git-lfs support --- .../src/main/java/sonia/scm/store/Blob.java | 9 + .../main/java/sonia/scm/store/FileBlob.java | 13 + scm-plugins/scm-git-plugin/pom.xml | 16 + .../sonia/scm/web/GitPermissionFilter.java | 22 +- .../java/sonia/scm/web/GitServletModule.java | 5 +- .../sonia/scm/web/GitUserAgentProvider.java | 23 +- .../java/sonia/scm/web/ScmGitServlet.java | 193 +++++++++--- .../scm/web/lfs/ScmBlobLfsRepository.java | 90 ++++++ .../web/lfs/servlet/LfsServletFactory.java | 98 ++++++ .../lfs/servlet/ScmFileTransferServlet.java | 278 ++++++++++++++++++ .../lfs/servlet/ScmLfsProtocolServlet.java | 26 ++ .../scm/web/GitPermissionFilterTest.java | 48 +++ .../scm/web/GitUserAgentProviderTest.java | 1 + .../java/sonia/scm/web/ScmGitServletTest.java | 26 ++ .../lfs/servlet/LfsServletFactoryTest.java | 75 +++++ .../servlet/ScmFileTransferServletTest.java | 42 +++ 16 files changed, 914 insertions(+), 51 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ScmBlobLfsRepository.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmLfsProtocolServlet.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/ScmGitServletTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/ScmFileTransferServletTest.java diff --git a/scm-core/src/main/java/sonia/scm/store/Blob.java b/scm-core/src/main/java/sonia/scm/store/Blob.java index 195697472a..a569e61ab5 100644 --- a/scm-core/src/main/java/sonia/scm/store/Blob.java +++ b/scm-core/src/main/java/sonia/scm/store/Blob.java @@ -85,4 +85,13 @@ public interface Blob * @throws IOException */ public OutputStream getOutputStream() throws IOException; + + /** + * + * Returns the size (in bytes) of the blob. + * @since 1.54 + */ + public long getSize(); + + } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/FileBlob.java b/scm-dao-xml/src/main/java/sonia/scm/store/FileBlob.java index 9c4cba88d9..33204536d5 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/FileBlob.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/FileBlob.java @@ -119,6 +119,19 @@ public final class FileBlob implements Blob return new FileOutputStream(file); } + @Override + public long getSize() { + if (this.file.isFile()) { + + return this.file.length(); + } else { + + //to sum up all other cases, in which we cannot determine a size + return -1; + + } + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 5ab04d1e1c..eb4469fcae 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -37,6 +37,12 @@ ${jgit.version} + + sonia.jgit + org.eclipse.jgit.lfs.server + ${jgit.version} + + commons-lang commons-lang @@ -51,6 +57,16 @@ 1.54-SNAPSHOT test + + sonia.scm + scm-dao-xml + 1.52-SNAPSHOT + + + sonia.scm + scm-dao-xml + 1.52-SNAPSHOT + diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java index f682c355a6..3ff18e210e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java @@ -35,6 +35,7 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -127,13 +128,22 @@ public class GitPermissionFilter extends ProviderPermissionFilter * @return */ @Override - protected boolean isWriteRequest(HttpServletRequest request) - { + protected boolean isWriteRequest(HttpServletRequest request) { + String uri = request.getRequestURI(); - return uri.endsWith(URI_RECEIVE_PACK) - || (uri.endsWith(URI_REF_INFO) - && PARAMETER_VALUE_RECEIVE.equals( - request.getParameter(PARAMETER_SERVICE))); + return uri.endsWith(URI_RECEIVE_PACK) || + (uri.endsWith(URI_REF_INFO) && PARAMETER_VALUE_RECEIVE.equals(request.getParameter(PARAMETER_SERVICE))) || + isLfsFileUpload(request); + } + + @VisibleForTesting + static boolean isLfsFileUpload(HttpServletRequest request) { + String regex = String.format("^%s%s/.+(\\.git)?/info/lfs/objects/[a-z0-9]{64}$", request.getContextPath(), GitServletModule.GIT_PATH); + return request.getRequestURI().matches(regex) && "PUT".equals(request.getMethod()); + + } + + } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java index fdb1ae52d9..3e85b4f3c2 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java @@ -49,8 +49,11 @@ import sonia.scm.plugin.ext.Extension; public class GitServletModule extends ServletModule { + public static final String GIT_PATH = "/git"; + /** Field description */ - public static final String PATTERN_GIT = "/git/*"; + public static final String PATTERN_GIT = GIT_PATH + "/*"; + //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java index 4fa7f15802..7d4111fa16 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java @@ -53,6 +53,12 @@ public class GitUserAgentProvider implements UserAgentProvider false).basicAuthenticationCharset( Charsets.UTF_8).build(); + @VisibleForTesting + static final UserAgent GIT_LFS = UserAgent.builder("Git Lfs") + .browser(false) + .basicAuthenticationCharset(Charsets.UTF_8) + .build(); + /** Field description */ @VisibleForTesting static final UserAgent MSYSGIT = UserAgent.builder("msysGit").browser( @@ -60,10 +66,11 @@ public class GitUserAgentProvider implements UserAgentProvider Charsets.UTF_8).build(); /** Field description */ - private static final String PREFIX = "git/"; + private static final String PREFIX_REGULAR = "git/"; + private static final String PREFIX_LFS = "git-lfs/"; /** Field description */ - private static final String SUFFIX = "msysgit"; + private static final String SUFFIX_MSYSGIT = "msysgit"; //~--- methods -------------------------------------------------------------- @@ -80,16 +87,14 @@ public class GitUserAgentProvider implements UserAgentProvider { UserAgent ua = null; - if (userAgentString.startsWith(PREFIX)) - { - if (userAgentString.contains(SUFFIX)) - { + if (userAgentString.startsWith(PREFIX_REGULAR)) { + if (userAgentString.contains(SUFFIX_MSYSGIT)) { ua = MSYSGIT; - } - else - { + } else { ua = GIT; } + } else if (userAgentString.startsWith(PREFIX_LFS)) { + ua = GIT_LFS; } return ua; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java index 686e4dfbd7..5d3798ec3d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java @@ -35,23 +35,31 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.jgit.http.server.GitServlet; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static org.slf4j.LoggerFactory.getLogger; + +import org.eclipse.jgit.lfs.lib.Constants; +import static org.eclipse.jgit.lfs.lib.Constants.CONTENT_TYPE_GIT_LFS_JSON; + +import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryProvider; import sonia.scm.repository.RepositoryRequestListenerUtil; import sonia.scm.util.HttpUtil; +import sonia.scm.web.lfs.servlet.LfsServletFactory; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import sonia.scm.repository.RepositoryException; @@ -73,7 +81,7 @@ public class ScmGitServlet extends GitServlet /** the logger for ScmGitServlet */ private static final Logger logger = - LoggerFactory.getLogger(ScmGitServlet.class); + getLogger(ScmGitServlet.class); //~--- constructors --------------------------------------------------------- @@ -90,14 +98,19 @@ public class ScmGitServlet extends GitServlet */ @Inject public ScmGitServlet(GitRepositoryResolver repositoryResolver, - GitReceivePackFactory receivePackFactory, - GitRepositoryViewer repositoryViewer, - RepositoryProvider repositoryProvider, - RepositoryRequestListenerUtil repositoryRequestListenerUtil) + GitReceivePackFactory receivePackFactory, + GitRepositoryViewer repositoryViewer, + RepositoryProvider repositoryProvider, + RepositoryRequestListenerUtil repositoryRequestListenerUtil, + LfsServletFactory lfsServletFactory, + UserAgentParser userAgentParser) { this.repositoryProvider = repositoryProvider; this.repositoryViewer = repositoryViewer; this.repositoryRequestListenerUtil = repositoryRequestListenerUtil; + this.lfsServletFactory = lfsServletFactory; + this.userAgentParser = userAgentParser; + setRepositoryResolver(repositoryResolver); setReceivePackFactory(receivePackFactory); } @@ -120,49 +133,44 @@ public class ScmGitServlet extends GitServlet throws ServletException, IOException { String uri = HttpUtil.getStrippedURI(request); + logger.trace("--request URI: {}", uri); - if (uri.matches(REGEX_GITHTTPBACKEND)) - { - sonia.scm.repository.Repository repository = repositoryProvider.get(); + //decide the type of response to be presented to the client + UserAgent userAgent = userAgentParser.parse(request); + if (userAgent.isBrowser()) { - if (repository != null) - { - if (repositoryRequestListenerUtil.callListeners(request, response, - repository)) - { - super.service(request, response); - } - else if (logger.isDebugEnabled()) - { + renderHtmlRepositryOverview(request, response); + } else { + + //service the request for a git client + final Repository repository = repositoryProvider.get(); + + if (repository == null) { + + //repository could not be matched found the current request + super.service(request, response); + } else { + + if (repositoryRequestListenerUtil.callListeners(request, response, repository)) { + handleRequest(request, response, repository); + } else if (logger.isDebugEnabled()) { logger.debug("request aborted by repository request listener"); } } - else - { - super.service(request, response); - } - } - else - { - printGitInformation(request, response); } } /** - * Method description - * - * - * + * This method renders basic information about the repository into the response. The result is meant to be viewed by + * browser. * @param request * @param response * * @throws IOException * @throws ServletException */ - private void printGitInformation(HttpServletRequest request, - HttpServletResponse response) - throws ServletException, IOException - { + private void renderHtmlRepositryOverview(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + sonia.scm.repository.Repository scmRepository = repositoryProvider.get(); if (scmRepository != null) @@ -186,6 +194,113 @@ public class ScmGitServlet extends GitServlet } } + /** + * Decides the type request being currently made and delegates it accordingly. + *
    + *
  • Batch API:
  • + *
      + *
    • used to provide the client with information on how handle the large files of a repository.
    • + *
    • response contains the information where to perform the actual upload and download of the large objects.
    • + *
    + *
  • Transfer API:
  • + *
      + *
    • receives and provides the actual large objects (resolves the pointer placed in the file of the working copy).
    • + *
    • invoked only after the Batch API has been questioned about what to do with the large files
    • + *
    + *
  • Regular HTTP Backend
  • + *
      + *
    • services everything that is not git-lfs.
    • + *
    + * + *
+ */ + private void handleRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { + + logger.trace("--- Repository is: {}", repository.getName()); + if (isLfsBatchApiRequest(request, repository.getName())) { + + logger.trace("--- detected LFS Batch API Request"); + HttpServlet servlet = lfsServletFactory.createProtocolServletFor(repository, request); + servlet.service(request, response); + } else if (isLfsFileTransferRequest(request, repository.getName())) { + + logger.trace("--- detected LFS File Transfer Request"); + HttpServlet servlet = lfsServletFactory.createFileLfsServletFor(repository, request); + servlet.service(request, response); + } else { + logger.trace("--- seems to be regular Git HTTP backend request: {}", request.getRequestURI()); + //continue to the regular HTTP Backend + super.service(request, response); + } + } + + /** + * Decides whether or not a request is for the LFS Batch API, + *

+ * - PUT or GET + * - exactly for this repository + * - Content Type is {@link Constants#HDR_APPLICATION_OCTET_STREAM}. + * + * @return Returns {@code false} if either of the conditions does not match. Returns true if all match. + */ + private static boolean isLfsFileTransferRequest(HttpServletRequest request, String repository) { + + String regex = String.format("^%s%s/%s(\\.git)?/info/lfs/objects/[a-z0-9]{64}$", request.getContextPath(), GitServletModule.GIT_PATH, repository); + boolean pathMatches = request.getRequestURI().matches(regex); + + boolean methodMatches = request.getMethod().equals("PUT") || request.getMethod().equals("GET"); + + return pathMatches && methodMatches; + } + + /** + * Decides whether or not a request is for the LFS Batch API, + *

+ * - POST + * - exactly for this repository + * - Content Type is {@link Constants#CONTENT_TYPE_GIT_LFS_JSON}. + * + * @return Returns {@code false} if either of the conditions does not match. Returns true if all match. + */ + private static boolean isLfsBatchApiRequest(HttpServletRequest request, String repository) { + + String regex = String.format("^%s%s/%s(\\.git)?/info/lfs/objects/batch$", request.getContextPath(), GitServletModule.GIT_PATH, repository); + boolean pathMatches = request.getRequestURI().matches(regex); + + boolean methodMatches = "POST".equals(request.getMethod()); + + boolean headerContentTypeMatches = isLfsContentHeaderField(request.getContentType(), CONTENT_TYPE_GIT_LFS_JSON); + boolean headerAcceptMatches = isLfsContentHeaderField(request.getHeader("Accept"), CONTENT_TYPE_GIT_LFS_JSON); + + return pathMatches && methodMatches && headerContentTypeMatches && headerAcceptMatches; + } + + /** + * Checks whether request is of the specific content type. + * + * @param request The HTTP request header value to be examined. + * @param expectedContentType The expected content type. + * @return Returns {@code true} if the request has the expected content type. Return {@code false} otherwise. + */ + @VisibleForTesting + static boolean isLfsContentHeaderField(String request, String expectedContentType) { + + if (request == null || request.isEmpty()) { + return false; + } + + String[] parts = request.split(" "); + for (String part : parts) { + if (part.startsWith(expectedContentType)) { + + return true; + } + } + + return false; + } + + //~--- fields --------------------------------------------------------------- /** Field description */ @@ -194,6 +309,14 @@ public class ScmGitServlet extends GitServlet /** Field description */ private final RepositoryRequestListenerUtil repositoryRequestListenerUtil; - /** Field description */ + /** + * Field description + */ private final GitRepositoryViewer repositoryViewer; + + private final LfsServletFactory lfsServletFactory; + + private final UserAgentParser userAgentParser; + + } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ScmBlobLfsRepository.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ScmBlobLfsRepository.java new file mode 100644 index 0000000000..46a58f6f07 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/ScmBlobLfsRepository.java @@ -0,0 +1,90 @@ +package sonia.scm.web.lfs; + +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.server.LargeFileRepository; +import org.eclipse.jgit.lfs.server.Response; +import sonia.scm.store.Blob; +import sonia.scm.store.BlobStore; + +import java.io.IOException; + +/** + * This LargeFileRepository is used for jGit-Servlet implementation. Under the jgit LFS Servlet hood, the + * SCM-Repository API is used to implement the Repository. + * + * @since 1.54 + * Created by omilke on 03.05.2017. + */ +public class ScmBlobLfsRepository implements LargeFileRepository { + + private final BlobStore blobStore; + + /** + * This URI is used to determine the actual URI for Upload / Download. Must be full URI (or rewritable by reverse + * proxy). + */ + private final String baseUri; + + /** + * Creates a {@link ScmBlobLfsRepository} for the provided repository. + * + * @param blobStore The SCM Blobstore used for this @{@link LargeFileRepository}. + * @param baseUri This URI is used to determine the actual URI for Upload / Download. Must be full URI (or + * rewritable by reverse proxy). + */ + + public ScmBlobLfsRepository(BlobStore blobStore, String baseUri) { + + this.blobStore = blobStore; + this.baseUri = baseUri; + } + + @Override + public Response.Action getDownloadAction(AnyLongObjectId id) { + + return getAction(id); + } + + @Override + public Response.Action getUploadAction(AnyLongObjectId id, long size) { + + return getAction(id); + } + + @Override + public Response.Action getVerifyAction(AnyLongObjectId id) { + + //validation is optional. We do not support it. + return null; + } + + @Override + public long getSize(AnyLongObjectId id) throws IOException { + + //this needs to be size of what is will be written into the response of the download. Clients are likely to + // verify it. + Blob blob = this.blobStore.get(id.getName()); + if (blob == null) { + + return -1; + } else { + + return blob.getSize(); + } + + } + + /** + * Constructs the Download / Upload actions to be supplied to the client. + */ + private Response.Action getAction(AnyLongObjectId id) { + + //LFS protocol has to provide the information on where to put or get the actual content, i. e. + //the actual URI for up- and download. + + Response.Action a = new Response.Action(); + a.href = baseUri + id.getName(); + + return a; + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java new file mode 100644 index 0000000000..cbe4d1b99c --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java @@ -0,0 +1,98 @@ +package sonia.scm.web.lfs.servlet; + +import com.google.common.annotations.VisibleForTesting; +import org.eclipse.jgit.lfs.server.LargeFileRepository; +import org.eclipse.jgit.lfs.server.LfsProtocolServlet; +import org.eclipse.jgit.lfs.server.fs.FileLfsServlet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.repository.Repository; +import sonia.scm.store.BlobStore; +import sonia.scm.store.BlobStoreFactory; +import sonia.scm.util.HttpUtil; +import sonia.scm.web.lfs.ScmBlobLfsRepository; + +import javax.inject.Inject; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; + +/** + * This factory class is a helper class to provide the {@link LfsProtocolServlet} and the {@link FileLfsServlet} + * belonging to a SCM Repository. + * + * @since 1.54 + * Created by omilke on 11.05.2017. + */ +public class LfsServletFactory { + + private static final String GIT_LFS_REPOSITORY_POSTFIX = "-git-lfs"; + + private static final Logger logger = LoggerFactory.getLogger(LfsServletFactory.class); + + private final BlobStoreFactory blobStoreFactory; + + @Inject + public LfsServletFactory(BlobStoreFactory blobStoreFactory) { + + this.blobStoreFactory = blobStoreFactory; + } + + /** + * Builds the {@link LfsProtocolServlet} (jgit API) for a SCM Repository. + * + * @param repository The SCM Repository to build the servlet for. + * @param request The {@link HttpServletRequest} the used to access the SCM Repository. + * @return The {@link LfsProtocolServlet} to provide the LFS Batch API for a SCM Repository. + */ + public LfsProtocolServlet createProtocolServletFor(Repository repository, HttpServletRequest request) { + + BlobStore blobStore = getBlobStore(repository); + String baseUri = buildBaseUri(repository, request); + + LargeFileRepository largeFileRepository = new ScmBlobLfsRepository(blobStore, baseUri); + return new ScmLfsProtocolServlet(largeFileRepository); + } + + /** + * Builds the {@link FileLfsServlet} (jgit API) for a SCM Repository. + * + * @param repository The SCM Repository to build the servlet for. + * @param request The {@link HttpServletRequest} the used to access the SCM Repository. + * @return The {@link FileLfsServlet} to provide the LFS Upload / Download API for a SCM Repository. + */ + public HttpServlet createFileLfsServletFor(Repository repository, HttpServletRequest request) { + + return new ScmFileTransferServlet(getBlobStore(repository)); + } + + /** + * Build the complete URI, under which the File Transfer API for this repository will be will be reachable. + * + * @param repository The repository to build the File Transfer URI for. + * @param request The request to construct the complete URI from. + */ + @VisibleForTesting + static String buildBaseUri(Repository repository, HttpServletRequest request) { + + return String.format("%s/git/%s.git/info/lfs/objects/", HttpUtil.getCompleteUrl(request), repository.getName()); + } + + /** + * Provides a {@link BlobStore} corresponding to the SCM Repository. + *

+ * git-lfs repositories should generally carry the same name as their regular SCM repository counterparts. However, + * we have decided to store them under their IDs instead of their names, since the names might change and provide + * other drawbacks, as well. + *

+ * These repositories will have {@linkplain #GIT_LFS_REPOSITORY_POSTFIX} appended to their IDs. + * + * @param repository The SCM Repository to provide a LFS {@link BlobStore} for. + */ + @VisibleForTesting + BlobStore getBlobStore(Repository repository) { + + return blobStoreFactory.getBlobStore(repository.getId() + GIT_LFS_REPOSITORY_POSTFIX); + } + + +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java new file mode 100644 index 0000000000..2d0026ac9a --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java @@ -0,0 +1,278 @@ +package sonia.scm.web.lfs.servlet; + +import com.google.common.annotations.VisibleForTesting; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.apache.http.HttpStatus; +import org.eclipse.jgit.lfs.errors.CorruptLongObjectException; +import org.eclipse.jgit.lfs.errors.InvalidLongObjectIdException; +import org.eclipse.jgit.lfs.lib.AnyLongObjectId; +import org.eclipse.jgit.lfs.lib.Constants; +import org.eclipse.jgit.lfs.lib.LongObjectId; +import org.eclipse.jgit.lfs.server.LfsProtocolServlet; +import org.eclipse.jgit.lfs.server.fs.FileLfsServlet; +import org.eclipse.jgit.lfs.server.internal.LfsServerText; +import org.eclipse.jgit.util.HttpSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.store.Blob; +import sonia.scm.store.BlobStore; +import sonia.scm.util.IOUtil; + +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.text.MessageFormat; + +/** + * This Servlet provides the upload and download of files via git-lfs. + *

+ * This implementation is based on {@link FileLfsServlet} but adjusted to work with + * servlet-2.5 instead of servlet-3.1. + *

+ * + * @see FileLfsServlet + * @since 1.54 + * Created by omilke on 15.05.2017. + */ +public class ScmFileTransferServlet extends HttpServlet { + + private static final Logger logger = LoggerFactory.getLogger(ScmFileTransferServlet.class); + + private static final long serialVersionUID = 1L; + + /** + * Gson is used because the implementation was based on the jgit implementation. However the {@link LfsProtocolServlet} (which we do use in + * {@link ScmLfsProtocolServlet}) also uses Gson, which currently ties us to Gson anyway. + */ + private static Gson gson = createGson(); + + private final BlobStore blobStore; + + public ScmFileTransferServlet(BlobStore store) { + + this.blobStore = store; + } + + + /** + * Extracts the part after the last slash from path. + * + * @return Returns {@code null} if the part after the last slash is itself {@code null} or if its length is not 64. + */ + @VisibleForTesting + static String objectIdFromPath(String info) { + + int lastSlash = info.lastIndexOf('/'); + String potentialObjectId = info.substring(lastSlash + 1); + + if (potentialObjectId.length() != 64) { + return null; + + } else { + return potentialObjectId; + } + } + + /** + * Logs the message and provides it to the client. + * + * @param response The response + * @param status The HTTP Status Code to be provided to the client. + * @param message the message to used for server-side logging. It is also provided to the client. + */ + private static void sendErrorAndLog(HttpServletResponse response, int status, String message) throws IOException { + + logger.warn("Error occurred during git-lfs file transfer: {}", message); + + sendError(response, status, message); + } + + /** + * Logs the exception and provides only the message of the exception to the client. + * + * @param response The response + * @param status The HTTP Status Code to be provided to the client. + * @param exception An exception to used for server-side logging. + */ + private static void sendErrorAndLog(HttpServletResponse response, int status, Exception exception) throws IOException { + + logger.warn("Error occurred during git-lfs file transfer.", exception); + String message = exception.getMessage(); + + + sendError(response, status, message); + } + + private static void sendError(HttpServletResponse response, int status, String message) throws IOException { + + try (PrintWriter writer = response.getWriter()) { + + gson.toJson(new Error(message), writer); + + response.setStatus(status); + writer.flush(); + } + + response.flushBuffer(); + } + + private static Gson createGson() { + + GsonBuilder gb = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).setPrettyPrinting().disableHtmlEscaping(); + return gb.create(); + } + + /** + * Provides a blob to download. + *

+ * Actual implementation is based on org.eclipse.jgit.lfs.server.fs.ObjectDownloadListener and adjusted + * to non-async as we're currently on servlet-2.5. + * + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + AnyLongObjectId objectId = getObjectToTransfer(request, response); + if (objectId == null) { + + logInvalidObjectId(request.getRequestURI()); + } else { + + final String objectIdName = objectId.getName(); + logger.trace("---- providing download for LFS-Oid: {}", objectIdName); + + Blob savedBlob = blobStore.get(objectIdName); + if (isBlobPresent(savedBlob)) { + + logger.trace("----- Object {}: providing {} bytes", objectIdName, savedBlob.getSize()); + writeBlobIntoResponse(savedBlob, response); + } else { + + sendErrorAndLog(response, HttpStatus.SC_NOT_FOUND, MessageFormat.format(LfsServerText.get().objectNotFound, objectIdName)); + } + } + } + + /** + * Receives a blob from an upload. + *

+ * Actual implementation is based on org.eclipse.jgit.lfs.server.fs.ObjectUploadListener and adjusted + * to non-async as we're currently on servlet-2.5. + * + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + @Override + protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + AnyLongObjectId objectId = getObjectToTransfer(request, response); + if (objectId == null) { + + logInvalidObjectId(request.getRequestURI()); + } else { + + logger.trace("---- receiving upload for LFS-Oid: {}", objectId.getName()); + readBlobFromResponse(request, response, objectId); + } + } + + /** + * Extracts the {@link LongObjectId} from the request. Finishes the request, in case the {@link LongObjectId} cannot + * be extracted with an appropriate error. + * + * @throws IOException Thrown if the response could not be completed in an error case. + */ + private AnyLongObjectId getObjectToTransfer(HttpServletRequest request, HttpServletResponse response) throws IOException { + + String path = request.getPathInfo(); + + String objectIdFromPath = objectIdFromPath(path); + if (objectIdFromPath == null) { + + //ObjectId is not retrievable from URL + sendErrorAndLog(response, HttpStatus.SC_UNPROCESSABLE_ENTITY, MessageFormat.format(LfsServerText.get().invalidPathInfo, path)); + return null; + } else { + try { + return LongObjectId.fromString(objectIdFromPath); + } catch (InvalidLongObjectIdException e) { + + sendErrorAndLog(response, HttpStatus.SC_UNPROCESSABLE_ENTITY, e); + return null; + } + } + } + + private void logInvalidObjectId(String requestURI) { + + logger.warn("---- could not extract Oid from Request. Path seems to be invalid: {}", requestURI); + } + + private boolean isBlobPresent(Blob savedBlob) { + + return savedBlob != null && savedBlob.getSize() >= 0; + } + + private void writeBlobIntoResponse(Blob savedBlob, HttpServletResponse response) throws IOException { + + try (ServletOutputStream responseOutputStream = response.getOutputStream(); + InputStream savedBlobInputStream = savedBlob.getInputStream()) { + + response.addHeader(HttpSupport.HDR_CONTENT_LENGTH, String.valueOf(savedBlob.getSize())); + response.setContentType(Constants.HDR_APPLICATION_OCTET_STREAM); + + IOUtil.copy(savedBlobInputStream, responseOutputStream); + } catch (IOException ex) { + + sendErrorAndLog(response, HttpStatus.SC_INTERNAL_SERVER_ERROR, ex); + } + + } + + private void readBlobFromResponse(HttpServletRequest request, HttpServletResponse response, AnyLongObjectId objectId) throws IOException { + + try (OutputStream blobOutputStream = blobStore.create(objectId.getName()).getOutputStream(); + ServletInputStream requestInputStream = request.getInputStream()) { + + IOUtil.copy(requestInputStream, blobOutputStream); + + response.setContentType(Constants.CONTENT_TYPE_GIT_LFS_JSON); + response.setStatus(HttpServletResponse.SC_OK); + } catch (CorruptLongObjectException ex) { + + sendErrorAndLog(response, HttpStatus.SC_BAD_REQUEST, ex); + } + + + } + + /** + * Used for providing an error message. + */ + private static class Error { + String message; + + Error(String m) { + + this.message = m; + } + } + +} + + diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmLfsProtocolServlet.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmLfsProtocolServlet.java new file mode 100644 index 0000000000..332cf12e09 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmLfsProtocolServlet.java @@ -0,0 +1,26 @@ +package sonia.scm.web.lfs.servlet; + +import org.eclipse.jgit.lfs.errors.LfsException; +import org.eclipse.jgit.lfs.server.LargeFileRepository; +import org.eclipse.jgit.lfs.server.LfsProtocolServlet; + +/** + * Provides an implementation for the git-lfs Batch API. + * + * @since 1.54 + * Created by omilke on 11.05.2017. + */ +public class ScmLfsProtocolServlet extends LfsProtocolServlet { + + private final LargeFileRepository repository; + + public ScmLfsProtocolServlet(LargeFileRepository largeFileRepository) { + this.repository = largeFileRepository; + } + + + @Override + protected LargeFileRepository getLargeFileRepository(LfsRequest request, String path) throws LfsException { + return repository; + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java new file mode 100644 index 0000000000..a107f35816 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java @@ -0,0 +1,48 @@ +package sonia.scm.web; + +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by omilke on 19.05.2017. + */ +public class GitPermissionFilterTest { + + @Test + public void isLfsFileUpload() throws Exception { + + HttpServletRequest mockedRequest = getRequestWithMethodAndPathInfo("PUT", + "/scm/git/git-lfs-demo.git/info/lfs/objects/8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec"); + assertThat((GitPermissionFilter.isLfsFileUpload(mockedRequest)), is(true)); + + mockedRequest = getRequestWithMethodAndPathInfo("GET", + "/scm/git/git-lfs-demo.git/info/lfs/objects/8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec"); + assertThat((GitPermissionFilter.isLfsFileUpload(mockedRequest)), is(false)); + + mockedRequest = getRequestWithMethodAndPathInfo("POST", + "/scm/git/git-lfs-demo.git/info/lfs/objects/8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec"); + assertThat((GitPermissionFilter.isLfsFileUpload(mockedRequest)), is(false)); + + mockedRequest = getRequestWithMethodAndPathInfo("POST", + "/scm/git/git-lfs-demo.git/info/lfs/objects/batch"); + assertThat((GitPermissionFilter.isLfsFileUpload(mockedRequest)), is(false)); + } + + private HttpServletRequest getRequestWithMethodAndPathInfo(String method, String pathInfo) { + + HttpServletRequest mock = mock(HttpServletRequest.class); + + when(mock.getMethod()).thenReturn(method); + when(mock.getRequestURI()).thenReturn(pathInfo); + when(mock.getContextPath()).thenReturn("/scm"); + + return mock; + } + +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java index b16580c4a5..67c2be69de 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java @@ -58,6 +58,7 @@ public class GitUserAgentProviderTest public void testParseUserAgent() { assertEquals(GitUserAgentProvider.GIT, parse("git/1.7.9.5")); + assertEquals(GitUserAgentProvider.GIT_LFS, parse("git-lfs/2.0.1 (GitHub; windows amd64; go 1.8; git 678cdbd4)")); assertEquals(GitUserAgentProvider.MSYSGIT, parse("git/1.8.3.msysgit.0")); assertNull(parse("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36")); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/ScmGitServletTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/ScmGitServletTest.java new file mode 100644 index 0000000000..4013980cb1 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/ScmGitServletTest.java @@ -0,0 +1,26 @@ +package sonia.scm.web; + +import org.junit.Test; + +import static org.eclipse.jgit.lfs.lib.Constants.CONTENT_TYPE_GIT_LFS_JSON; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * Created by omilke on 11.05.2017. + */ +public class ScmGitServletTest { + + @Test + public void isContentTypeMatches() throws Exception { + + assertThat(ScmGitServlet.isLfsContentHeaderField("application/vnd.git-lfs+json", CONTENT_TYPE_GIT_LFS_JSON), is(true)); + assertThat(ScmGitServlet.isLfsContentHeaderField("application/vnd.git-lfs+json;", CONTENT_TYPE_GIT_LFS_JSON), is(true)); + assertThat(ScmGitServlet.isLfsContentHeaderField("application/vnd.git-lfs+json; charset=utf-8", CONTENT_TYPE_GIT_LFS_JSON), is(true)); + + assertThat(ScmGitServlet.isLfsContentHeaderField("application/vnd.git-lfs-json;", CONTENT_TYPE_GIT_LFS_JSON), is(false)); + assertThat(ScmGitServlet.isLfsContentHeaderField("", CONTENT_TYPE_GIT_LFS_JSON), is(false)); + assertThat(ScmGitServlet.isLfsContentHeaderField(null, CONTENT_TYPE_GIT_LFS_JSON), is(false)); + } + +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java new file mode 100644 index 0000000000..09b3b13082 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java @@ -0,0 +1,75 @@ +package sonia.scm.web.lfs.servlet; + +import org.junit.Test; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.store.BlobStoreFactory; + +import javax.servlet.http.HttpServletRequest; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; +import static org.mockito.Matchers.matches; +import static org.mockito.Mockito.*; + +/** + * Created by omilke on 18.05.2017. + */ +public class LfsServletFactoryTest { + + @Test + public void buildBaseUri() throws Exception { + + String repositoryName = "git-lfs-demo"; + + String result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryName), RequestWithUri(repositoryName, true)); + assertThat(result, is(equalTo("http://localhost:8081/scm/git/git-lfs-demo.git/info/lfs/objects/"))); + + + //result will be with dot-gix suffix, ide + result = LfsServletFactory.buildBaseUri(new Repository("", "GIT", repositoryName), RequestWithUri(repositoryName, false)); + assertThat(result, is(equalTo("http://localhost:8081/scm/git/git-lfs-demo.git/info/lfs/objects/"))); + } + + @Test + public void getBlobStore() throws Exception { + + BlobStoreFactory blobStoreFactoryMock = mock(BlobStoreFactory.class); + + //TODO #239: + RepositoryTestData repositoryTestData; + + + new LfsServletFactory(blobStoreFactoryMock).getBlobStore(new Repository("the-id", "GIT", "the-name")); + + //just make sure the right parameter is passed, as properly validating the return value is nearly impossible with the return value (and should not be + // part of this test) + verify(blobStoreFactoryMock).getBlobStore(matches("the-id-git-lfs")); + + //make sure there have been no further usages of the factory + verifyNoMoreInteractions(blobStoreFactoryMock); + } + + private HttpServletRequest RequestWithUri(String repositoryName, boolean withDotGitSuffix) { + + HttpServletRequest mockedRequest = mock(HttpServletRequest.class); + + final String suffix; + if (withDotGitSuffix) { + suffix = ".git"; + } else { + suffix = ""; + } + + //build from valid live request data + when(mockedRequest.getRequestURL()).thenReturn( + new StringBuffer(String.format("http://localhost:8081/scm/git/%s%s/info/lfs/objects/batch", repositoryName, suffix))); + when(mockedRequest.getRequestURI()).thenReturn(String.format("/scm/git/%s%s/info/lfs/objects/batch", repositoryName, suffix)); + when(mockedRequest.getContextPath()).thenReturn("/scm"); + + return mockedRequest; + } + + +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/ScmFileTransferServletTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/ScmFileTransferServletTest.java new file mode 100644 index 0000000000..3caa02b272 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/ScmFileTransferServletTest.java @@ -0,0 +1,42 @@ +package sonia.scm.web.lfs.servlet; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.*; + +/** + * Created by omilke on 16.05.2017. + */ +public class ScmFileTransferServletTest { + + @Test + public void hasObjectId() throws Exception { + + String SAMPLE_OBJECT_ID = "8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec"; + + String path = "/git-lfs-demo.git/info/lfs/objects/" + SAMPLE_OBJECT_ID; + assertThat(ScmFileTransferServlet.objectIdFromPath(path), is(equalTo(SAMPLE_OBJECT_ID))); + + path = "/" + SAMPLE_OBJECT_ID; + assertThat(ScmFileTransferServlet.objectIdFromPath(path), is(equalTo(SAMPLE_OBJECT_ID))); + + path = SAMPLE_OBJECT_ID; + assertThat(ScmFileTransferServlet.objectIdFromPath(path), is(equalTo(SAMPLE_OBJECT_ID))); + + String nonObjectId = "this-ist-last-to-found"; + path = "/git-lfs-demo.git/info/lfs/objects/" + nonObjectId; + assertThat(ScmFileTransferServlet.objectIdFromPath(path), is(nullValue())); + + nonObjectId = SAMPLE_OBJECT_ID.substring(1); + path = "/git-lfs-demo.git/info/lfs/objects/" + nonObjectId; + assertThat(ScmFileTransferServlet.objectIdFromPath(path), is(nullValue())); + + nonObjectId = SAMPLE_OBJECT_ID + "X"; + path = "/git-lfs-demo.git/info/lfs/objects/" + nonObjectId; + assertThat(ScmFileTransferServlet.objectIdFromPath(path), is(nullValue())); + + } +} From f6318a3b5846dff3a906f044aa341497c3caf39e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 May 2017 09:21:50 +0200 Subject: [PATCH 32/56] fix missing commit of blob after lfs push --- .../sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java index 2d0026ac9a..324140b432 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/ScmFileTransferServlet.java @@ -246,10 +246,12 @@ public class ScmFileTransferServlet extends HttpServlet { private void readBlobFromResponse(HttpServletRequest request, HttpServletResponse response, AnyLongObjectId objectId) throws IOException { - try (OutputStream blobOutputStream = blobStore.create(objectId.getName()).getOutputStream(); + Blob blob = blobStore.create(objectId.getName()); + try (OutputStream blobOutputStream = blob.getOutputStream(); ServletInputStream requestInputStream = request.getInputStream()) { IOUtil.copy(requestInputStream, blobOutputStream); + blob.commit(); response.setContentType(Constants.CONTENT_TYPE_GIT_LFS_JSON); response.setStatus(HttpServletResponse.SC_OK); From eb7872bb3ff7035ad765004dfb75fac337a195c3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 May 2017 09:29:50 +0200 Subject: [PATCH 33/56] execute CreateRepositoryITCase for git, subversion and mercurial --- .../test/java/sonia/scm/it/CreateRepositoriesITCase.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scm-webapp/src/test/java/sonia/scm/it/CreateRepositoriesITCase.java b/scm-webapp/src/test/java/sonia/scm/it/CreateRepositoriesITCase.java index 6ef841b2df..3d240c96f5 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/CreateRepositoriesITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/CreateRepositoriesITCase.java @@ -76,8 +76,6 @@ public class CreateRepositoriesITCase extends AbstractAdminITCaseBase */ public CreateRepositoriesITCase(String repositoryType) { - System.out.append("==> CreateRepositoriesITCase - ").println( - repositoryType); this.repositoryType = repositoryType; } @@ -92,14 +90,14 @@ public class CreateRepositoriesITCase extends AbstractAdminITCaseBase @Parameters public static Collection createParameters() { - Collection params = new ArrayList(); + Collection params = new ArrayList<>(); params.add(new String[] { "git" }); - params.add(new String[] { "git" }); + params.add(new String[] { "svn" }); if (IOUtil.search("hg") != null) { - params.add(new String[] { "git" }); + params.add(new String[] { "hg" }); } return params; From 964973d8f76df2502e1163fc3cc82e62a6654f00 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 May 2017 09:48:12 +0200 Subject: [PATCH 34/56] added jgit detection to GitUserAgentProvider, to fix integration tests --- .../sonia/scm/web/GitUserAgentProvider.java | 92 +++++++++++-------- .../scm/web/GitUserAgentProviderTest.java | 1 + 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java index 7d4111fa16..c75bbb1c9c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java @@ -35,6 +35,8 @@ package sonia.scm.web; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; +import com.google.common.base.Strings; +import java.util.Locale; import sonia.scm.plugin.ext.Extension; @@ -46,57 +48,75 @@ import sonia.scm.plugin.ext.Extension; @Extension public class GitUserAgentProvider implements UserAgentProvider { + + private static final String PREFIX_JGIT = "jgit/"; + private static final String PREFIX_REGULAR = "git/"; + private static final String PREFIX_LFS = "git-lfs/"; + private static final String SUFFIX_MSYSGIT = "msysgit"; - /** Field description */ @VisibleForTesting - static final UserAgent GIT = UserAgent.builder("Git").browser( - false).basicAuthenticationCharset( - Charsets.UTF_8).build(); + static final UserAgent JGIT = UserAgent.builder("JGit") + .browser(false) + .basicAuthenticationCharset(Charsets.UTF_8) + .build(); + + @VisibleForTesting + static final UserAgent GIT = UserAgent.builder("Git") + .browser(false) + .basicAuthenticationCharset(Charsets.UTF_8) + .build(); @VisibleForTesting static final UserAgent GIT_LFS = UserAgent.builder("Git Lfs") - .browser(false) - .basicAuthenticationCharset(Charsets.UTF_8) - .build(); + .browser(false) + .basicAuthenticationCharset(Charsets.UTF_8) + .build(); - /** Field description */ @VisibleForTesting - static final UserAgent MSYSGIT = UserAgent.builder("msysGit").browser( - false).basicAuthenticationCharset( - Charsets.UTF_8).build(); + static final UserAgent MSYSGIT = UserAgent.builder("msysGit") + .browser(false) + .basicAuthenticationCharset(Charsets.UTF_8) + .build(); - /** Field description */ - private static final String PREFIX_REGULAR = "git/"; - private static final String PREFIX_LFS = "git-lfs/"; - /** Field description */ - private static final String SUFFIX_MSYSGIT = "msysgit"; //~--- methods -------------------------------------------------------------- - /** - * Method description - * - * - * @param userAgentString - * - * @return - */ @Override public UserAgent parseUserAgent(String userAgentString) { - UserAgent ua = null; - - if (userAgentString.startsWith(PREFIX_REGULAR)) { - if (userAgentString.contains(SUFFIX_MSYSGIT)) { - ua = MSYSGIT; - } else { - ua = GIT; - } - } else if (userAgentString.startsWith(PREFIX_LFS)) { - ua = GIT_LFS; + String lowerUserAgent = toLower(userAgentString); + + if (isJGit(lowerUserAgent)) { + return JGIT; + } else if (isMsysGit(lowerUserAgent)) { + return MSYSGIT; + } else if (isGitLFS(lowerUserAgent)) { + return GIT_LFS; + } else if (isGit(lowerUserAgent)) { + return GIT; + } else { + return null; } - - return ua; + } + + private String toLower(String value) { + return Strings.nullToEmpty(value).toLowerCase(Locale.ENGLISH); + } + + private boolean isJGit(String userAgent) { + return userAgent.startsWith(PREFIX_JGIT); + } + + private boolean isMsysGit(String userAgent) { + return userAgent.startsWith(PREFIX_REGULAR) && userAgent.contains(SUFFIX_MSYSGIT); + } + + private boolean isGitLFS(String userAgent) { + return userAgent.startsWith(PREFIX_LFS); + } + + private boolean isGit(String userAgent) { + return userAgent.startsWith(PREFIX_REGULAR); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java index 67c2be69de..bdb607ecb1 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java @@ -58,6 +58,7 @@ public class GitUserAgentProviderTest public void testParseUserAgent() { assertEquals(GitUserAgentProvider.GIT, parse("git/1.7.9.5")); + assertEquals(GitUserAgentProvider.JGIT, parse("jgit/4.5.2")); assertEquals(GitUserAgentProvider.GIT_LFS, parse("git-lfs/2.0.1 (GitHub; windows amd64; go 1.8; git 678cdbd4)")); assertEquals(GitUserAgentProvider.MSYSGIT, parse("git/1.8.3.msysgit.0")); assertNull(parse("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36")); From 8b92175fbc7b78cff00273dfea898f39ba7d164e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 May 2017 12:05:01 +0200 Subject: [PATCH 35/56] improved structure of GitUserAgentProvider and added more unit tests --- .../sonia/scm/web/GitUserAgentProvider.java | 17 ++++--- .../scm/web/GitUserAgentProviderTest.java | 49 +++++++++---------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java index c75bbb1c9c..c407453540 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java @@ -41,18 +41,14 @@ import java.util.Locale; import sonia.scm.plugin.ext.Extension; /** - * + * UserAgent provider for git related clients. * @author Sebastian Sdorra * @since 1.45 */ @Extension -public class GitUserAgentProvider implements UserAgentProvider -{ +public class GitUserAgentProvider implements UserAgentProvider { private static final String PREFIX_JGIT = "jgit/"; - private static final String PREFIX_REGULAR = "git/"; - private static final String PREFIX_LFS = "git-lfs/"; - private static final String SUFFIX_MSYSGIT = "msysgit"; @VisibleForTesting static final UserAgent JGIT = UserAgent.builder("JGit") @@ -60,11 +56,15 @@ public class GitUserAgentProvider implements UserAgentProvider .basicAuthenticationCharset(Charsets.UTF_8) .build(); + private static final String PREFIX_REGULAR = "git/"; + @VisibleForTesting static final UserAgent GIT = UserAgent.builder("Git") .browser(false) .basicAuthenticationCharset(Charsets.UTF_8) .build(); + + private static final String PREFIX_LFS = "git-lfs/"; @VisibleForTesting static final UserAgent GIT_LFS = UserAgent.builder("Git Lfs") @@ -72,6 +72,8 @@ public class GitUserAgentProvider implements UserAgentProvider .basicAuthenticationCharset(Charsets.UTF_8) .build(); + private static final String SUFFIX_MSYSGIT = "msysgit"; + @VisibleForTesting static final UserAgent MSYSGIT = UserAgent.builder("msysGit") .browser(false) @@ -83,8 +85,7 @@ public class GitUserAgentProvider implements UserAgentProvider //~--- methods -------------------------------------------------------------- @Override - public UserAgent parseUserAgent(String userAgentString) - { + public UserAgent parseUserAgent(String userAgentString) { String lowerUserAgent = toLower(userAgentString); if (isJGit(lowerUserAgent)) { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java index bdb607ecb1..61e89de190 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java @@ -41,22 +41,17 @@ import static org.junit.Assert.*; //~--- JDK imports ------------------------------------------------------------ -import java.util.Locale; - /** - * + * Unit tests for {@link GitUserAgentProvider}. + * * @author Sebastian Sdorra */ -public class GitUserAgentProviderTest -{ +public class GitUserAgentProviderTest { - /** - * Method description - * - */ + private final GitUserAgentProvider provider = new GitUserAgentProvider(); + @Test - public void testParseUserAgent() - { + public void testParseUserAgent() { assertEquals(GitUserAgentProvider.GIT, parse("git/1.7.9.5")); assertEquals(GitUserAgentProvider.JGIT, parse("jgit/4.5.2")); assertEquals(GitUserAgentProvider.GIT_LFS, parse("git-lfs/2.0.1 (GitHub; windows amd64; go 1.8; git 678cdbd4)")); @@ -64,22 +59,22 @@ public class GitUserAgentProviderTest assertNull(parse("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36")); } - /** - * Method description - * - * - * @param v - * - * @return - */ - private UserAgent parse(String v) - { - return provider.parseUserAgent( - Strings.nullToEmpty(v).toLowerCase(Locale.ENGLISH)); + @Test + public void testParseUserAgentCaseSensitive() { + assertEquals(GitUserAgentProvider.GIT, parse("Git/1.7.9.5")); + } + + @Test + public void testParseUserAgentWithEmptyValue() { + assertNull(parse(null)); + } + + @Test + public void testParseUserAgentWithNullValue() { + assertNull(parse(null)); } - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private final GitUserAgentProvider provider = new GitUserAgentProvider(); + private UserAgent parse(String v) { + return provider.parseUserAgent(v); + } } From 880b0499e8ac6d425200311ca44601ef460b2435 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 30 May 2017 14:10:43 +0200 Subject: [PATCH 36/56] use uri to decide type of request instead of user-agent --- .../java/sonia/scm/web/ScmGitServlet.java | 137 +++++++++--------- 1 file changed, 66 insertions(+), 71 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java index 5d3798ec3d..89d9c4b034 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java @@ -102,14 +102,12 @@ public class ScmGitServlet extends GitServlet GitRepositoryViewer repositoryViewer, RepositoryProvider repositoryProvider, RepositoryRequestListenerUtil repositoryRequestListenerUtil, - LfsServletFactory lfsServletFactory, - UserAgentParser userAgentParser) + LfsServletFactory lfsServletFactory) { this.repositoryProvider = repositoryProvider; this.repositoryViewer = repositoryViewer; this.repositoryRequestListenerUtil = repositoryRequestListenerUtil; this.lfsServletFactory = lfsServletFactory; - this.userAgentParser = userAgentParser; setRepositoryResolver(repositoryResolver); setReceivePackFactory(receivePackFactory); @@ -131,34 +129,74 @@ public class ScmGitServlet extends GitServlet protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException - { - String uri = HttpUtil.getStrippedURI(request); - logger.trace("--request URI: {}", uri); - - //decide the type of response to be presented to the client - UserAgent userAgent = userAgentParser.parse(request); - if (userAgent.isBrowser()) { - - renderHtmlRepositryOverview(request, response); + { + Repository repository = repositoryProvider.get(); + if (repository != null) { + handleRequest(request, response, repository); } else { - - //service the request for a git client - final Repository repository = repositoryProvider.get(); - - if (repository == null) { - - //repository could not be matched found the current request - super.service(request, response); - } else { - - if (repositoryRequestListenerUtil.callListeners(request, response, repository)) { - handleRequest(request, response, repository); - } else if (logger.isDebugEnabled()) { - logger.debug("request aborted by repository request listener"); - } - } + // logger + response.sendError(HttpServletResponse.SC_NOT_FOUND); } } + + /** + * Decides the type request being currently made and delegates it accordingly. + *

    + *
  • Batch API:
  • + *
      + *
    • used to provide the client with information on how handle the large files of a repository.
    • + *
    • response contains the information where to perform the actual upload and download of the large objects.
    • + *
    + *
  • Transfer API:
  • + *
      + *
    • receives and provides the actual large objects (resolves the pointer placed in the file of the working copy).
    • + *
    • invoked only after the Batch API has been questioned about what to do with the large files
    • + *
    + *
  • Regular Git Http API:
  • + *
      + *
    • regular git http wire protocol, use by normal git clients.
    • + *
    + *
  • Browser Overview:
  • + *
      + *
    • short repository overview for browser clients.
    • + *
    + *
  • + *
+ */ + private void handleRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { + + logger.trace("--- Repository is: {}", repository.getName()); + if (isLfsBatchApiRequest(request, repository.getName())) { + + logger.trace("--- detected LFS Batch API Request"); + HttpServlet servlet = lfsServletFactory.createProtocolServletFor(repository, request); + handleGitRequest(servlet, request, response, repository); + } else if (isLfsFileTransferRequest(request, repository.getName())) { + + logger.trace("--- detected LFS File Transfer Request"); + HttpServlet servlet = lfsServletFactory.createFileLfsServletFor(repository, request); + handleGitRequest(servlet, request, response, repository); + } else if (isRegularGitAPIRequest(request)) { + logger.trace("--- seems to be regular Git HTTP backend request: {}", request.getRequestURI()); + // continue with the regular git Backend + handleGitRequest(this, request, response, repository); + } else { + renderHtmlRepositryOverview(request, response); + } + } + + private boolean isRegularGitAPIRequest(HttpServletRequest request) { + return HttpUtil.getStrippedURI(request).matches(REGEX_GITHTTPBACKEND); + } + + private void handleGitRequest(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { + if (repositoryRequestListenerUtil.callListeners(request, response, repository)) { + servlet.service(request, response); + } else if (logger.isDebugEnabled()) { + logger.debug("request aborted by repository request listener"); + } + } + /** * This method renders basic information about the repository into the response. The result is meant to be viewed by @@ -194,46 +232,6 @@ public class ScmGitServlet extends GitServlet } } - /** - * Decides the type request being currently made and delegates it accordingly. - *
    - *
  • Batch API:
  • - *
      - *
    • used to provide the client with information on how handle the large files of a repository.
    • - *
    • response contains the information where to perform the actual upload and download of the large objects.
    • - *
    - *
  • Transfer API:
  • - *
      - *
    • receives and provides the actual large objects (resolves the pointer placed in the file of the working copy).
    • - *
    • invoked only after the Batch API has been questioned about what to do with the large files
    • - *
    - *
  • Regular HTTP Backend
  • - *
      - *
    • services everything that is not git-lfs.
    • - *
    - * - *
- */ - private void handleRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { - - logger.trace("--- Repository is: {}", repository.getName()); - if (isLfsBatchApiRequest(request, repository.getName())) { - - logger.trace("--- detected LFS Batch API Request"); - HttpServlet servlet = lfsServletFactory.createProtocolServletFor(repository, request); - servlet.service(request, response); - } else if (isLfsFileTransferRequest(request, repository.getName())) { - - logger.trace("--- detected LFS File Transfer Request"); - HttpServlet servlet = lfsServletFactory.createFileLfsServletFor(repository, request); - servlet.service(request, response); - } else { - logger.trace("--- seems to be regular Git HTTP backend request: {}", request.getRequestURI()); - //continue to the regular HTTP Backend - super.service(request, response); - } - } - /** * Decides whether or not a request is for the LFS Batch API, *

@@ -316,7 +314,4 @@ public class ScmGitServlet extends GitServlet private final LfsServletFactory lfsServletFactory; - private final UserAgentParser userAgentParser; - - } From 2af11b1f9ccc62ec22490a0504f0c68a08e25cbc Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 1 Jun 2017 10:11:42 +0200 Subject: [PATCH 37/56] relax git lfs write request check and improved tests for GitPermissionFilter --- .../sonia/scm/web/GitPermissionFilter.java | 28 ++-- .../scm/web/GitPermissionFilterTest.java | 125 ++++++++++++++---- 2 files changed, 117 insertions(+), 36 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java index 3ff18e210e..7442aa8620 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java @@ -73,6 +73,8 @@ public class GitPermissionFilter extends ProviderPermissionFilter /** Field description */ public static final String URI_REF_INFO = "/info/refs"; + + public static final String METHOD_LFS_UPLOAD = "PUT"; //~--- constructors --------------------------------------------------------- @@ -129,21 +131,23 @@ public class GitPermissionFilter extends ProviderPermissionFilter */ @Override protected boolean isWriteRequest(HttpServletRequest request) { - - String uri = request.getRequestURI(); - - return uri.endsWith(URI_RECEIVE_PACK) || - (uri.endsWith(URI_REF_INFO) && PARAMETER_VALUE_RECEIVE.equals(request.getParameter(PARAMETER_SERVICE))) || - isLfsFileUpload(request); - + return isReceivePackRequest(request) || + isReceiveServiceRequest(request) || + isLfsFileUpload(request); + } + + private boolean isReceivePackRequest(HttpServletRequest request) { + return request.getRequestURI().endsWith(URI_RECEIVE_PACK); + } + + private boolean isReceiveServiceRequest(HttpServletRequest request) { + return request.getRequestURI().endsWith(URI_REF_INFO) + && PARAMETER_VALUE_RECEIVE.equals(request.getParameter(PARAMETER_SERVICE)); } @VisibleForTesting - static boolean isLfsFileUpload(HttpServletRequest request) { - String regex = String.format("^%s%s/.+(\\.git)?/info/lfs/objects/[a-z0-9]{64}$", request.getContextPath(), GitServletModule.GIT_PATH); - return request.getRequestURI().matches(regex) && "PUT".equals(request.getMethod()); - + private static boolean isLfsFileUpload(HttpServletRequest request) { + return METHOD_LFS_UPLOAD.equalsIgnoreCase(request.getMethod()); } - } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java index a107f35816..69fc72fcc7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java @@ -1,48 +1,125 @@ package sonia.scm.web; +import com.google.common.base.Charsets; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import javax.servlet.ServletOutputStream; import org.junit.Test; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.repository.RepositoryProvider; /** + * Unit tests for {@link GitPermissionFilter}. + * * Created by omilke on 19.05.2017. */ +@RunWith(MockitoJUnitRunner.class) public class GitPermissionFilterTest { + @Mock + private RepositoryProvider repositoryProvider; + + private final GitPermissionFilter permissionFilter = new GitPermissionFilter( + new ScmConfiguration(), repositoryProvider + ); + + @Mock + private HttpServletResponse response; + @Test - public void isLfsFileUpload() throws Exception { - - HttpServletRequest mockedRequest = getRequestWithMethodAndPathInfo("PUT", - "/scm/git/git-lfs-demo.git/info/lfs/objects/8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec"); - assertThat((GitPermissionFilter.isLfsFileUpload(mockedRequest)), is(true)); - - mockedRequest = getRequestWithMethodAndPathInfo("GET", - "/scm/git/git-lfs-demo.git/info/lfs/objects/8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec"); - assertThat((GitPermissionFilter.isLfsFileUpload(mockedRequest)), is(false)); - - mockedRequest = getRequestWithMethodAndPathInfo("POST", - "/scm/git/git-lfs-demo.git/info/lfs/objects/8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec"); - assertThat((GitPermissionFilter.isLfsFileUpload(mockedRequest)), is(false)); - - mockedRequest = getRequestWithMethodAndPathInfo("POST", - "/scm/git/git-lfs-demo.git/info/lfs/objects/batch"); - assertThat((GitPermissionFilter.isLfsFileUpload(mockedRequest)), is(false)); + public void testIsWriteRequest() { + HttpServletRequest request = mockRequestWithMethodAndRequestURI("POST", "/scm/git/fanzy-project/git-receive-pack"); + assertThat(permissionFilter.isWriteRequest(request), is(true)); + + request = mockRequestWithMethodAndRequestURI("GET", "/scm/git/fanzy-project/info/refs?service=git-receive-pack"); + assertThat(permissionFilter.isWriteRequest(request), is(true)); + + request = mockRequestWithMethodAndRequestURI("GET", "/scm/git/fanzy-project/info/refs?service=some-other-service"); + assertThat(permissionFilter.isWriteRequest(request), is(false)); + + request = mockRequestWithMethodAndRequestURI( + "PUT", + "/scm/git/git-lfs-demo.git/info/lfs/objects/8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec" + ); + assertThat(permissionFilter.isWriteRequest(request), is(true)); + + request = mockRequestWithMethodAndRequestURI( + "GET", + "/scm/git/git-lfs-demo.git/info/lfs/objects/8fcebeb5698230685f92028e560f8f1683ebc15ec82a620ffad5c12a3c19bdec" + ); + assertThat(permissionFilter.isWriteRequest(request), is(false)); + + request = mockRequestWithMethodAndRequestURI("POST", "/scm/git/git-lfs-demo.git/info/lfs/objects/batch"); + assertThat(permissionFilter.isWriteRequest(request), is(false)); } - private HttpServletRequest getRequestWithMethodAndPathInfo(String method, String pathInfo) { - + private HttpServletRequest mockRequestWithMethodAndRequestURI(String method, String requestURI) { HttpServletRequest mock = mock(HttpServletRequest.class); when(mock.getMethod()).thenReturn(method); - when(mock.getRequestURI()).thenReturn(pathInfo); + when(mock.getRequestURI()).thenReturn(requestURI); when(mock.getContextPath()).thenReturn("/scm"); return mock; } + @Test + public void testSendNotEnoughPrivilegesErrorAsBrowser() throws IOException { + HttpServletRequest request = mockGitReceivePackServiceRequest(); + + permissionFilter.sendNotEnoughPrivilegesError(request, response); + + verify(response).sendError(HttpServletResponse.SC_FORBIDDEN); + } + + @Test + public void testSendNotEnoughPrivilegesErrorAsGitClient() throws IOException { + HttpServletRequest request = mockGitReceivePackServiceRequest(); + when(request.getHeader("User-Agent")).thenReturn("git/2.9.3"); + + CapturingServletOutputStream stream = new CapturingServletOutputStream(); + when(response.getOutputStream()).thenReturn(stream); + + permissionFilter.sendNotEnoughPrivilegesError(request, response); + + verify(response).setStatus(HttpServletResponse.SC_OK); + assertThat(stream.toString(), containsString("privileges")); + } + + private HttpServletRequest mockGitReceivePackServiceRequest() { + HttpServletRequest request = mockRequestWithMethodAndRequestURI("GET", "/git/info/refs"); + when(request.getParameter("service")).thenReturn("git-receive-pack"); + return request; + } + + private static class CapturingServletOutputStream extends ServletOutputStream { + + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + @Override + public void write(int b) throws IOException { + baos.write(b); + } + + @Override + public void close() throws IOException { + baos.close(); + } + + @Override + public String toString() { + return baos.toString(); + } + } + } From ee4a19365eed098e43f1850d8f0326f9805ab669 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 1 Jun 2017 16:07:18 +0200 Subject: [PATCH 38/56] fix possible stackoverflow in git request handling --- .../java/sonia/scm/web/ScmGitServlet.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java index 89d9c4b034..6b3e41b27e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java @@ -95,6 +95,7 @@ public class ScmGitServlet extends GitServlet * @param repositoryViewer * @param repositoryProvider * @param repositoryRequestListenerUtil + * @param lfsServletFactory */ @Inject public ScmGitServlet(GitRepositoryResolver repositoryResolver, @@ -164,22 +165,19 @@ public class ScmGitServlet extends GitServlet * */ private void handleRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { - logger.trace("--- Repository is: {}", repository.getName()); if (isLfsBatchApiRequest(request, repository.getName())) { logger.trace("--- detected LFS Batch API Request"); - HttpServlet servlet = lfsServletFactory.createProtocolServletFor(repository, request); - handleGitRequest(servlet, request, response, repository); + handleGitLfsRequest(request, response, repository); } else if (isLfsFileTransferRequest(request, repository.getName())) { logger.trace("--- detected LFS File Transfer Request"); - HttpServlet servlet = lfsServletFactory.createFileLfsServletFor(repository, request); - handleGitRequest(servlet, request, response, repository); + handleGitLfsRequest(request, response, repository); } else if (isRegularGitAPIRequest(request)) { logger.trace("--- seems to be regular Git HTTP backend request: {}", request.getRequestURI()); // continue with the regular git Backend - handleGitRequest(this, request, response, repository); + handleRegularGitRequest(request, response, repository); } else { renderHtmlRepositryOverview(request, response); } @@ -189,7 +187,8 @@ public class ScmGitServlet extends GitServlet return HttpUtil.getStrippedURI(request).matches(REGEX_GITHTTPBACKEND); } - private void handleGitRequest(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { + private void handleGitLfsRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { + HttpServlet servlet = lfsServletFactory.createProtocolServletFor(repository, request); if (repositoryRequestListenerUtil.callListeners(request, response, repository)) { servlet.service(request, response); } else if (logger.isDebugEnabled()) { @@ -197,6 +196,14 @@ public class ScmGitServlet extends GitServlet } } + private void handleRegularGitRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { + if (repositoryRequestListenerUtil.callListeners(request, response, repository)) { + super.service(request, response); + } else if (logger.isDebugEnabled()) { + logger.debug("request aborted by repository request listener"); + } + } + /** * This method renders basic information about the repository into the response. The result is meant to be viewed by From d9486ba8ba3cc6a35a7c339cff6705d281625d20 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 1 Jun 2017 16:08:07 +0200 Subject: [PATCH 39/56] improve git client detection at GitPermissionFilter to include jgit --- .../java/sonia/scm/repository/GitUtil.java | 5 +- .../sonia/scm/web/GitPermissionFilter.java | 61 +++++-------------- .../sonia/scm/web/GitUserAgentProvider.java | 2 +- .../sonia/scm/repository/GitUtilTest.java | 20 ++++++ .../scm/web/GitPermissionFilterTest.java | 14 ++++- .../scm/web/GitUserAgentProviderTest.java | 2 - 6 files changed, 52 insertions(+), 52 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index ef0f12c27d..087a17f60e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java @@ -70,6 +70,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; +import sonia.scm.web.GitUserAgentProvider; /** * @@ -77,6 +78,8 @@ import javax.servlet.http.HttpServletRequest; */ public final class GitUtil { + + private static final GitUserAgentProvider GIT_USER_AGENT_PROVIDER = new GitUserAgentProvider(); /** Field description */ public static final String REF_HEAD = "HEAD"; @@ -698,7 +701,7 @@ public final class GitUtil */ public static boolean isGitClient(HttpServletRequest request) { - return HttpUtil.userAgentStartsWith(request, USERAGENT_GIT); + return GIT_USER_AGENT_PROVIDER.parseUserAgent(request.getHeader(HttpUtil.HEADER_USERAGENT)) != null; } /** diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java index 7442aa8620..9e809242c9 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java @@ -55,80 +55,49 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** - * + * GitPermissionFilter decides if a git request requires write or read privileges. + * * @author Sebastian Sdorra */ @Singleton public class GitPermissionFilter extends ProviderPermissionFilter { - /** Field description */ - public static final String PARAMETER_SERVICE = "service"; + private static final String PARAMETER_SERVICE = "service"; - /** Field description */ - public static final String PARAMETER_VALUE_RECEIVE = "git-receive-pack"; + private static final String PARAMETER_VALUE_RECEIVE = "git-receive-pack"; - /** Field description */ - public static final String URI_RECEIVE_PACK = "git-receive-pack"; + private static final String URI_RECEIVE_PACK = "git-receive-pack"; - /** Field description */ - public static final String URI_REF_INFO = "/info/refs"; + private static final String URI_REF_INFO = "/info/refs"; - public static final String METHOD_LFS_UPLOAD = "PUT"; + private static final String METHOD_LFS_UPLOAD = "PUT"; //~--- constructors --------------------------------------------------------- /** - * Constructs ... + * Constructs a new instance of the GitPermissionFilter. * - * @param configuration - * @param repositoryProvider + * @param configuration scm main configuration + * @param repositoryProvider repository provider */ @Inject - public GitPermissionFilter(ScmConfiguration configuration, - RepositoryProvider repositoryProvider) - { + public GitPermissionFilter(ScmConfiguration configuration, RepositoryProvider repositoryProvider) { super(configuration, repositoryProvider); } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * @param response - * - * @throws IOException - */ @Override - protected void sendNotEnoughPrivilegesError(HttpServletRequest request, - HttpServletResponse response) - throws IOException - { - if (GitUtil.isGitClient(request)) - { + protected void sendNotEnoughPrivilegesError(HttpServletRequest request, HttpServletResponse response) + throws IOException { + if (GitUtil.isGitClient(request)) { GitSmartHttpTools.sendError(request, response, HttpServletResponse.SC_FORBIDDEN, ClientMessages.get(request).notEnoughPrivileges()); - } - else - { + } else { super.sendNotEnoughPrivilegesError(request, response); } } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * - * @return - */ @Override protected boolean isWriteRequest(HttpServletRequest request) { return isReceivePackRequest(request) || diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java index c407453540..ad56d5ea91 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java @@ -41,7 +41,7 @@ import java.util.Locale; import sonia.scm.plugin.ext.Extension; /** - * UserAgent provider for git related clients. + * UserAgent provider for git related clients. * @author Sebastian Sdorra * @since 1.45 */ diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitUtilTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitUtilTest.java index fdaa825a3f..f2ef35cf0f 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitUtilTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitUtilTest.java @@ -42,8 +42,10 @@ import static org.mockito.Mockito.*; import java.io.File; import java.io.IOException; +import javax.servlet.http.HttpServletRequest; import static org.junit.Assert.*; +import sonia.scm.util.HttpUtil; /** * Unit tests for {@link GitUtil}. @@ -114,4 +116,22 @@ public class GitUtilTest return repo; } + + @Test + public void testIsGitClient() { + HttpServletRequest request = mockRequestWithUserAgent("Git/2.9.3"); + assertTrue(GitUtil.isGitClient(request)); + + request = mockRequestWithUserAgent("JGit/2.9.3"); + assertTrue(GitUtil.isGitClient(request)); + + request = mockRequestWithUserAgent("Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) ..."); + assertFalse(GitUtil.isGitClient(request)); + } + + private HttpServletRequest mockRequestWithUserAgent(String userAgent) { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader(HttpUtil.HEADER_USERAGENT)).thenReturn(userAgent); + return request; + } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java index 69fc72fcc7..df4827a9fc 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitPermissionFilterTest.java @@ -17,6 +17,7 @@ import static org.mockito.Mockito.*; import org.mockito.runners.MockitoJUnitRunner; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.RepositoryProvider; +import sonia.scm.util.HttpUtil; /** * Unit tests for {@link GitPermissionFilter}. @@ -84,8 +85,17 @@ public class GitPermissionFilterTest { @Test public void testSendNotEnoughPrivilegesErrorAsGitClient() throws IOException { + verifySendNotEnoughPrivilegesErrorAsGitClient("git/2.9.3"); + } + + @Test + public void testSendNotEnoughPrivilegesErrorAsJGitClient() throws IOException { + verifySendNotEnoughPrivilegesErrorAsGitClient("JGit/4.2"); + } + + private void verifySendNotEnoughPrivilegesErrorAsGitClient(String userAgent) throws IOException { HttpServletRequest request = mockGitReceivePackServiceRequest(); - when(request.getHeader("User-Agent")).thenReturn("git/2.9.3"); + when(request.getHeader(HttpUtil.HEADER_USERAGENT)).thenReturn(userAgent); CapturingServletOutputStream stream = new CapturingServletOutputStream(); when(response.getOutputStream()).thenReturn(stream); @@ -93,7 +103,7 @@ public class GitPermissionFilterTest { permissionFilter.sendNotEnoughPrivilegesError(request, response); verify(response).setStatus(HttpServletResponse.SC_OK); - assertThat(stream.toString(), containsString("privileges")); + assertThat(stream.toString(), containsString("privileges")); } private HttpServletRequest mockGitReceivePackServiceRequest() { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java index 61e89de190..7b9fe944b5 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitUserAgentProviderTest.java @@ -33,8 +33,6 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Strings; - import org.junit.Test; import static org.junit.Assert.*; From 2a8cfc00d885563cdda887a27156e4ec1613c869 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 1 Jun 2017 16:27:20 +0200 Subject: [PATCH 40/56] use pattern and matcher instead of string matches, to improve performance --- .../java/sonia/scm/web/ScmGitServlet.java | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java index 6b3e41b27e..5dcef505c1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java @@ -57,6 +57,7 @@ import sonia.scm.web.lfs.servlet.LfsServletFactory; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; +import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -73,15 +74,15 @@ public class ScmGitServlet extends GitServlet { /** Field description */ - public static final String REGEX_GITHTTPBACKEND = - "(?x)^/git/(.*/(HEAD|info/refs|objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\\.(pack|idx))|git-(upload|receive)-pack))$"; + public static final Pattern REGEX_GITHTTPBACKEND = Pattern.compile( + "(?x)^/git/(.*/(HEAD|info/refs|objects/(info/[^/]+|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\\.(pack|idx))|git-(upload|receive)-pack))$" + ); /** Field description */ private static final long serialVersionUID = -7712897339207470674L; /** the logger for ScmGitServlet */ - private static final Logger logger = - getLogger(ScmGitServlet.class); + private static final Logger logger = getLogger(ScmGitServlet.class); //~--- constructors --------------------------------------------------------- @@ -165,26 +166,27 @@ public class ScmGitServlet extends GitServlet * */ private void handleRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { - logger.trace("--- Repository is: {}", repository.getName()); + logger.trace("handle git repository at {}", repository.getName()); if (isLfsBatchApiRequest(request, repository.getName())) { - logger.trace("--- detected LFS Batch API Request"); + logger.trace("handle lfs batch api request"); handleGitLfsRequest(request, response, repository); } else if (isLfsFileTransferRequest(request, repository.getName())) { - logger.trace("--- detected LFS File Transfer Request"); + logger.trace("handle lfs file transfer request"); handleGitLfsRequest(request, response, repository); } else if (isRegularGitAPIRequest(request)) { - logger.trace("--- seems to be regular Git HTTP backend request: {}", request.getRequestURI()); + logger.trace("handle regular git request"); // continue with the regular git Backend handleRegularGitRequest(request, response, repository); } else { - renderHtmlRepositryOverview(request, response); + logger.trace("handle browser request"); + handleBrowserRequest(request, response, repository); } } private boolean isRegularGitAPIRequest(HttpServletRequest request) { - return HttpUtil.getStrippedURI(request).matches(REGEX_GITHTTPBACKEND); + return REGEX_GITHTTPBACKEND.matcher(HttpUtil.getStrippedURI(request)).matches(); } private void handleGitLfsRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { @@ -214,28 +216,11 @@ public class ScmGitServlet extends GitServlet * @throws IOException * @throws ServletException */ - private void renderHtmlRepositryOverview(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - - sonia.scm.repository.Repository scmRepository = repositoryProvider.get(); - - if (scmRepository != null) - { - try - { - repositoryViewer.handleRequest(request, response, scmRepository); - } - catch (RepositoryException ex) - { - throw new ServletException("could not create repository view", ex); - } - catch (IOException ex) - { - throw new ServletException("could not create repository view", ex); - } - } - else - { - response.sendError(HttpServletResponse.SC_NOT_FOUND); + private void handleBrowserRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { + try { + repositoryViewer.handleRequest(request, response, repository); + } catch (RepositoryException | IOException ex) { + throw new ServletException("could not create repository view", ex); } } From 1effc9c29bde60e9a5143770ebcfb8eb9bf01124 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 1 Jun 2017 17:28:59 +0200 Subject: [PATCH 41/56] remove all items from lfs blob store, if the corresponding repository was removed --- .../java/sonia/scm/web/GitServletModule.java | 5 + .../java/sonia/scm/web/ScmGitServlet.java | 11 +- .../scm/web/lfs/LfsBlobStoreFactory.java | 80 ++++++++++++ .../scm/web/lfs/LfsStoreRemoveListener.java | 97 ++++++++++++++ .../web/lfs/servlet/LfsServletFactory.java | 38 ++---- .../scm/web/lfs/LfsBlobStoreFactoryTest.java | 72 +++++++++++ .../web/lfs/LfsStoreRemoveListenerTest.java | 122 ++++++++++++++++++ .../lfs/servlet/LfsServletFactoryTest.java | 22 ---- 8 files changed, 389 insertions(+), 58 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsBlobStoreFactory.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsStoreRemoveListener.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsStoreRemoveListenerTest.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java index 3e85b4f3c2..9ac3b78b20 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitServletModule.java @@ -40,6 +40,8 @@ import com.google.inject.servlet.ServletModule; import org.eclipse.jgit.transport.ScmTransportProtocol; import sonia.scm.plugin.ext.Extension; +import sonia.scm.web.lfs.LfsBlobStoreFactory; +import sonia.scm.web.lfs.LfsStoreRemoveListener; /** * @@ -68,6 +70,9 @@ public class GitServletModule extends ServletModule bind(GitRepositoryResolver.class); bind(GitReceivePackFactory.class); bind(ScmTransportProtocol.class); + + bind(LfsBlobStoreFactory.class); + bind(LfsStoreRemoveListener.class); // serlvelts and filters filter(PATTERN_GIT).through(GitBasicAuthenticationFilter.class); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java index 5dcef505c1..e8e62b52cc 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/ScmGitServlet.java @@ -168,13 +168,13 @@ public class ScmGitServlet extends GitServlet private void handleRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { logger.trace("handle git repository at {}", repository.getName()); if (isLfsBatchApiRequest(request, repository.getName())) { - + HttpServlet servlet = lfsServletFactory.createProtocolServletFor(repository, request); logger.trace("handle lfs batch api request"); - handleGitLfsRequest(request, response, repository); + handleGitLfsRequest(servlet, request, response, repository); } else if (isLfsFileTransferRequest(request, repository.getName())) { - + HttpServlet servlet = lfsServletFactory.createFileLfsServletFor(repository, request); logger.trace("handle lfs file transfer request"); - handleGitLfsRequest(request, response, repository); + handleGitLfsRequest(servlet, request, response, repository); } else if (isRegularGitAPIRequest(request)) { logger.trace("handle regular git request"); // continue with the regular git Backend @@ -189,8 +189,7 @@ public class ScmGitServlet extends GitServlet return REGEX_GITHTTPBACKEND.matcher(HttpUtil.getStrippedURI(request)).matches(); } - private void handleGitLfsRequest(HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { - HttpServlet servlet = lfsServletFactory.createProtocolServletFor(repository, request); + private void handleGitLfsRequest(HttpServlet servlet, HttpServletRequest request, HttpServletResponse response, Repository repository) throws ServletException, IOException { if (repositoryRequestListenerUtil.callListeners(request, response, repository)) { servlet.service(request, response); } else if (logger.isDebugEnabled()) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsBlobStoreFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsBlobStoreFactory.java new file mode 100644 index 0000000000..eebd6b8f2b --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsBlobStoreFactory.java @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + +package sonia.scm.web.lfs; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import sonia.scm.repository.Repository; +import sonia.scm.store.BlobStore; +import sonia.scm.store.BlobStoreFactory; + +/** + * Creates {@link BlobStore} objects to store lfs objects. + * + * @author Sebastian Sdorra + * @since 1.54 + */ +@Singleton +public class LfsBlobStoreFactory { + + private static final String GIT_LFS_REPOSITORY_POSTFIX = "-git-lfs"; + + private final BlobStoreFactory blobStoreFactory; + + /** + * Create a new instance. + * + * @param blobStoreFactory blob store factory + */ + @Inject + public LfsBlobStoreFactory(BlobStoreFactory blobStoreFactory) { + this.blobStoreFactory = blobStoreFactory; + } + + /** + * Provides a {@link BlobStore} corresponding to the SCM Repository. + *

+ * git-lfs repositories should generally carry the same name as their regular SCM repository counterparts. However, + * we have decided to store them under their IDs instead of their names, since the names might change and provide + * other drawbacks, as well. + *

+ * These repositories will have {@linkplain #GIT_LFS_REPOSITORY_POSTFIX} appended to their IDs. + * + * @param repository The SCM Repository to provide a LFS {@link BlobStore} for. + * + * @return blob store for the corresponding scm repository + */ + public BlobStore getLfsBlobStore(Repository repository) { + return blobStoreFactory.getBlobStore(repository.getId() + GIT_LFS_REPOSITORY_POSTFIX); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsStoreRemoveListener.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsStoreRemoveListener.java new file mode 100644 index 0000000000..4d86c38db6 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/LfsStoreRemoveListener.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + +package sonia.scm.web.lfs; + +import com.google.common.eventbus.Subscribe; +import com.google.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.EagerSingleton; +import sonia.scm.HandlerEvent; +import sonia.scm.plugin.ext.Extension; +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryEvent; +import sonia.scm.store.Blob; +import sonia.scm.store.BlobStore; + +/** + * Listener which removes all lfs objects from a blob store, whenever its corresponding git repository gets deleted. + * + * @author Sebastian Sdorra + * @since 1.54 + */ +@Extension +@EagerSingleton +public class LfsStoreRemoveListener { + + private static final Logger LOG = LoggerFactory.getLogger(LfsBlobStoreFactory.class); + + private final LfsBlobStoreFactory lfsBlobStoreFactory; + + @Inject + public LfsStoreRemoveListener(LfsBlobStoreFactory lfsBlobStoreFactory) { + this.lfsBlobStoreFactory = lfsBlobStoreFactory; + } + + /** + * Remove all object from the blob store, if the event is an delete event and the repository is a git repository. + * + * @param event repository event + */ + @Subscribe + public void handleRepositoryEvent(RepositoryEvent event) { + if ( isDeleteEvent(event) && isGitRepositoryEvent(event) ) { + removeLfsStore(event.getItem()); + } + } + + private boolean isDeleteEvent(RepositoryEvent event) { + return HandlerEvent.DELETE == event.getEventType(); + } + + private boolean isGitRepositoryEvent(RepositoryEvent event) { + return event.getItem() != null + && event.getItem().getType().equals(GitRepositoryHandler.TYPE_NAME); + } + + private void removeLfsStore(Repository repository) { + LOG.debug("remove all blobs from store, because corresponding git repository {} was removed", repository.getName()); + BlobStore blobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); + for ( Blob blob : blobStore.getAll() ) { + LOG.trace("remove blob {}, because repository {} was removed", blob.getId(), repository.getName()); + blobStore.remove(blob); + } + } + +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java index cbe4d1b99c..2ca10559a2 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/lfs/servlet/LfsServletFactory.java @@ -8,13 +8,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Repository; import sonia.scm.store.BlobStore; -import sonia.scm.store.BlobStoreFactory; import sonia.scm.util.HttpUtil; import sonia.scm.web.lfs.ScmBlobLfsRepository; import javax.inject.Inject; +import javax.inject.Singleton; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; +import sonia.scm.web.lfs.LfsBlobStoreFactory; /** * This factory class is a helper class to provide the {@link LfsProtocolServlet} and the {@link FileLfsServlet} @@ -23,18 +24,16 @@ import javax.servlet.http.HttpServletRequest; * @since 1.54 * Created by omilke on 11.05.2017. */ +@Singleton public class LfsServletFactory { - private static final String GIT_LFS_REPOSITORY_POSTFIX = "-git-lfs"; - private static final Logger logger = LoggerFactory.getLogger(LfsServletFactory.class); - private final BlobStoreFactory blobStoreFactory; + private final LfsBlobStoreFactory lfsBlobStoreFactory; @Inject - public LfsServletFactory(BlobStoreFactory blobStoreFactory) { - - this.blobStoreFactory = blobStoreFactory; + public LfsServletFactory(LfsBlobStoreFactory lfsBlobStoreFactory) { + this.lfsBlobStoreFactory = lfsBlobStoreFactory; } /** @@ -45,8 +44,7 @@ public class LfsServletFactory { * @return The {@link LfsProtocolServlet} to provide the LFS Batch API for a SCM Repository. */ public LfsProtocolServlet createProtocolServletFor(Repository repository, HttpServletRequest request) { - - BlobStore blobStore = getBlobStore(repository); + BlobStore blobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); String baseUri = buildBaseUri(repository, request); LargeFileRepository largeFileRepository = new ScmBlobLfsRepository(blobStore, baseUri); @@ -61,8 +59,7 @@ public class LfsServletFactory { * @return The {@link FileLfsServlet} to provide the LFS Upload / Download API for a SCM Repository. */ public HttpServlet createFileLfsServletFor(Repository repository, HttpServletRequest request) { - - return new ScmFileTransferServlet(getBlobStore(repository)); + return new ScmFileTransferServlet(lfsBlobStoreFactory.getLfsBlobStore(repository)); } /** @@ -73,26 +70,7 @@ public class LfsServletFactory { */ @VisibleForTesting static String buildBaseUri(Repository repository, HttpServletRequest request) { - return String.format("%s/git/%s.git/info/lfs/objects/", HttpUtil.getCompleteUrl(request), repository.getName()); } - /** - * Provides a {@link BlobStore} corresponding to the SCM Repository. - *

- * git-lfs repositories should generally carry the same name as their regular SCM repository counterparts. However, - * we have decided to store them under their IDs instead of their names, since the names might change and provide - * other drawbacks, as well. - *

- * These repositories will have {@linkplain #GIT_LFS_REPOSITORY_POSTFIX} appended to their IDs. - * - * @param repository The SCM Repository to provide a LFS {@link BlobStore} for. - */ - @VisibleForTesting - BlobStore getBlobStore(Repository repository) { - - return blobStoreFactory.getBlobStore(repository.getId() + GIT_LFS_REPOSITORY_POSTFIX); - } - - } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java new file mode 100644 index 0000000000..2eb8968405 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsBlobStoreFactoryTest.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + +package sonia.scm.web.lfs; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import static org.mockito.Matchers.matches; +import org.mockito.Mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.Repository; +import sonia.scm.store.BlobStoreFactory; + +/** + * Unit tests for {@link LfsBlobStoreFactory}. + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class LfsBlobStoreFactoryTest { + + @Mock + private BlobStoreFactory blobStoreFactory; + + @InjectMocks + private LfsBlobStoreFactory lfsBlobStoreFactory; + + @Test + public void getBlobStore() throws Exception { + lfsBlobStoreFactory.getLfsBlobStore(new Repository("the-id", "GIT", "the-name")); + + // just make sure the right parameter is passed, as properly validating the return value is nearly impossible with + // the return value (and should not be part of this test) + verify(blobStoreFactory).getBlobStore(matches("the-id-git-lfs")); + + // make sure there have been no further usages of the factory + verifyNoMoreInteractions(blobStoreFactory); + } + +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsStoreRemoveListenerTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsStoreRemoveListenerTest.java new file mode 100644 index 0000000000..36c380e193 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/LfsStoreRemoveListenerTest.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + +package sonia.scm.web.lfs; + +import com.google.common.collect.Lists; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.HandlerEvent; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryEvent; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.store.Blob; +import sonia.scm.store.BlobStore; + +/** + * Unit tests for {@link LfsStoreRemoveListener}. + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class LfsStoreRemoveListenerTest { + + @Mock + private LfsBlobStoreFactory lfsBlobStoreFactory; + + @Mock + private BlobStore blobStore; + + @InjectMocks + private LfsStoreRemoveListener lfsStoreRemoveListener; + + @Test + public void testHandleRepositoryEventWithNonDeleteEvents() { + lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEvent.BEFORE_CREATE)); + lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEvent.CREATE)); + + lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEvent.BEFORE_MODIFY)); + lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEvent.MODIFY)); + + lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEvent.BEFORE_DELETE)); + + verifyZeroInteractions(lfsBlobStoreFactory); + } + + @Test + public void testHandleRepositoryEventWithNonGitRepositories() { + lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEvent.DELETE, "svn")); + lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEvent.DELETE, "hg")); + lfsStoreRemoveListener.handleRepositoryEvent(event(HandlerEvent.DELETE, "dummy")); + + verifyZeroInteractions(lfsBlobStoreFactory); + } + + @Test + public void testHandleRepositoryEvent() { + Repository heartOfGold = RepositoryTestData.createHeartOfGold("git"); + + when(lfsBlobStoreFactory.getLfsBlobStore(heartOfGold)).thenReturn(blobStore); + Blob blobA = mockBlob("a"); + Blob blobB = mockBlob("b"); + List blobs = Lists.newArrayList(blobA, blobB); + when(blobStore.getAll()).thenReturn(blobs); + + + lfsStoreRemoveListener.handleRepositoryEvent(new RepositoryEvent(heartOfGold, HandlerEvent.DELETE)); + verify(blobStore).getAll(); + verify(blobStore).remove(blobA); + verify(blobStore).remove(blobB); + + verifyNoMoreInteractions(blobStore); + } + + private Blob mockBlob(String id) { + Blob blob = mock(Blob.class); + when(blob.getId()).thenReturn(id); + return blob; + } + + private RepositoryEvent event(HandlerEvent eventType) { + return event(eventType, "git"); + } + + private RepositoryEvent event(HandlerEvent eventType, String repositoryType) { + return new RepositoryEvent(RepositoryTestData.create42Puzzle(repositoryType), eventType); + } + +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java index 09b3b13082..c670032089 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/lfs/servlet/LfsServletFactoryTest.java @@ -2,15 +2,12 @@ package sonia.scm.web.lfs.servlet; import org.junit.Test; import sonia.scm.repository.Repository; -import sonia.scm.repository.RepositoryTestData; -import sonia.scm.store.BlobStoreFactory; import javax.servlet.http.HttpServletRequest; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; -import static org.mockito.Matchers.matches; import static org.mockito.Mockito.*; /** @@ -32,25 +29,6 @@ public class LfsServletFactoryTest { assertThat(result, is(equalTo("http://localhost:8081/scm/git/git-lfs-demo.git/info/lfs/objects/"))); } - @Test - public void getBlobStore() throws Exception { - - BlobStoreFactory blobStoreFactoryMock = mock(BlobStoreFactory.class); - - //TODO #239: - RepositoryTestData repositoryTestData; - - - new LfsServletFactory(blobStoreFactoryMock).getBlobStore(new Repository("the-id", "GIT", "the-name")); - - //just make sure the right parameter is passed, as properly validating the return value is nearly impossible with the return value (and should not be - // part of this test) - verify(blobStoreFactoryMock).getBlobStore(matches("the-id-git-lfs")); - - //make sure there have been no further usages of the factory - verifyNoMoreInteractions(blobStoreFactoryMock); - } - private HttpServletRequest RequestWithUri(String repositoryName, boolean withDotGitSuffix) { HttpServletRequest mockedRequest = mock(HttpServletRequest.class); From a59c352e2d08f1f1926d22f04a541c75a95e257f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 1 Jun 2017 18:03:12 +0200 Subject: [PATCH 42/56] update jgit to v4.5.2.201704071617-r-scm1 --- pom.xml | 2 +- scm-plugins/scm-git-plugin/pom.xml | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 4d256d8e1e..5b0b532bbd 100644 --- a/pom.xml +++ b/pom.xml @@ -424,7 +424,7 @@ 1.3.0 - v4.5.0.201609210915-r-scm1 + v4.5.2.201704071617-r-scm1 1.8.15-scm1 diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index eb4469fcae..4782bce9fe 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -57,16 +57,6 @@ 1.54-SNAPSHOT test - - sonia.scm - scm-dao-xml - 1.52-SNAPSHOT - - - sonia.scm - scm-dao-xml - 1.52-SNAPSHOT - From 0677af133671f38261029024708d0ccec90c701a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 1 Jun 2017 22:07:45 +0200 Subject: [PATCH 43/56] shiro-unit should be in test scope --- scm-webapp/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 2ea9bbca4b..0c12b51ed2 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -358,6 +358,7 @@ com.github.sdorra shiro-unit 1.0.0 + test From 84b15fdf897000a54277e9e1748c9ca47d5a0252 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 2 Jun 2017 14:18:41 +0200 Subject: [PATCH 44/56] implemented integration tests for lfs --- .../test/java/sonia/scm/it/GitLfsITCase.java | 343 ++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java diff --git a/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java new file mode 100644 index 0000000000..f8adb90ff1 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/it/GitLfsITCase.java @@ -0,0 +1,343 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + +package sonia.scm.it; + +import com.google.common.base.Charsets; +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.UniformInterfaceException; +import java.io.IOException; +import java.util.UUID; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import org.apache.shiro.crypto.hash.Sha256Hash; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.xc.JaxbAnnotationIntrospector; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Test; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import static org.junit.Assert.*; +import org.junit.rules.ExpectedException; + +import static sonia.scm.it.IntegrationTestUtil.*; +import static sonia.scm.it.RepositoryITUtil.*; +import sonia.scm.repository.Permission; +import sonia.scm.repository.PermissionType; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.user.User; +import sonia.scm.user.UserTestData; + +/** + * Integration tests for git lfs. + * + * @author Sebastian Sdorra + */ +public class GitLfsITCase { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private final ObjectMapper mapper = new ObjectMapper(); + + private Client adminClient; + + private Repository repository; + + public GitLfsITCase() { + mapper.setAnnotationIntrospector(new JaxbAnnotationIntrospector()); + } + + // lifecycle methods + + @Before + public void setUpTestDependencies() { + adminClient = createAdminClient(); + repository = createRepository(adminClient, RepositoryTestData.createHeartOfGold("git")); + } + + @After + public void tearDownTestDependencies() { + deleteRepository(adminClient, repository.getId()); + adminClient.destroy(); + } + + // tests + + @Test + public void testLfsAPIWithAdminPermissions() throws IOException { + uploadAndDownload(adminClient); + } + + @Test + public void testLfsAPIWithOwnerPermissions() throws IOException { + uploadAndDownloadAsUser(PermissionType.OWNER); + } + + private void uploadAndDownloadAsUser(PermissionType permissionType) throws IOException { + User trillian = UserTestData.createTrillian(); + trillian.setPassword("secret123"); + createUser(trillian); + + try { + repository.getPermissions().add(new Permission(trillian.getId(), permissionType)); + modifyRepository(repository); + + Client client = createClient(); + authenticate(client, trillian.getId(), "secret123"); + + uploadAndDownload(client); + } finally { + removeUser(trillian); + } + } + + @Test + public void testLfsAPIWithWritePermissions() throws IOException { + uploadAndDownloadAsUser(PermissionType.WRITE); + } + + private void createUser(User user) { + adminClient.resource(REST_BASE_URL + "users.json").post(user); + } + + private void modifyRepository(Repository repository) { + adminClient.resource(REST_BASE_URL + "repositories/" + repository.getId() + ".json").put(repository); + } + + private void removeUser(User user) { + adminClient.resource(REST_BASE_URL + "users/" + user.getId() + ".json").delete(); + } + + @Test + public void testLfsAPIWithoutWritePermissions() throws IOException { + User trillian = UserTestData.createTrillian(); + trillian.setPassword("secret123"); + createUser(trillian); + + expectedException.expect(UniformInterfaceException.class); + expectedException.expectMessage(Matchers.containsString("403")); + + + try { + repository.getPermissions().add(new Permission(trillian.getId(), PermissionType.READ)); + modifyRepository(repository); + + Client client = createClient(); + authenticate(client, trillian.getId(), "secret123"); + + uploadAndDownload(client); + } finally { + removeUser(trillian); + } + } + + @Test + public void testLfsDownloadWithReadPermissions() throws IOException { + User trillian = UserTestData.createTrillian(); + trillian.setPassword("secret123"); + createUser(trillian); + + + try { + repository.getPermissions().add(new Permission(trillian.getId(), PermissionType.READ)); + modifyRepository(repository); + + // upload data as admin + String data = UUID.randomUUID().toString(); + byte[] dataAsBytes = data.getBytes(Charsets.UTF_8); + LfsObject lfsObject = upload(adminClient, dataAsBytes); + + Client client = createClient(); + authenticate(client, trillian.getId(), "secret123"); + + // download as user + byte[] downloadedData = download(client, lfsObject); + + // assert both are equal + assertArrayEquals(dataAsBytes, downloadedData); + } finally { + removeUser(trillian); + } + } + + // lfs api + + private void uploadAndDownload(Client client) throws IOException { + String data = UUID.randomUUID().toString(); + byte[] dataAsBytes = data.getBytes(Charsets.UTF_8); + LfsObject lfsObject = upload(client, dataAsBytes); + byte[] downloadedData = download(client, lfsObject); + assertArrayEquals(dataAsBytes, downloadedData); + } + + private LfsObject upload(Client client, byte[] data) throws IOException { + LfsObject lfsObject = createLfsObject(data); + LfsRequestBody request = LfsRequestBody.createUploadRequest(lfsObject); + LfsResponseBody response = request(client, request); + + String uploadURL = response.objects[0].actions.upload.href; + client.resource(uploadURL).put(data); + + return lfsObject; + } + + private LfsResponseBody request(Client client, LfsRequestBody request) throws IOException { + String batchUrl = createBatchUrl(); + String requestAsString = mapper.writeValueAsString(request); + + return client + .resource(batchUrl) + .accept("application/vnd.git-lfs+json") + .header("Content-Type", "application/vnd.git-lfs+json") + .post(LfsResponseBody.class, requestAsString); + } + + private String createBatchUrl() { + String url = repository.createUrl(BASE_URL); + return url + "/info/lfs/objects/batch"; + } + + private byte[] download(Client client, LfsObject lfsObject) throws IOException { + LfsRequestBody request = LfsRequestBody.createDownloadRequest(lfsObject); + LfsResponseBody response = request(client, request); + + String downloadUrl = response.objects[0].actions.download.href; + return client.resource(downloadUrl).get(byte[].class); + } + + private LfsObject createLfsObject(byte[] data) { + Sha256Hash hash = new Sha256Hash(data); + String oid = hash.toHex(); + return new LfsObject(oid, data.length); + } + + // LFS DTO objects + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + private static class LfsRequestBody { + + private String operation; + private String[] transfers = new String[]{ "basic" }; + private LfsObject[] objects; + + public LfsRequestBody() { + } + + private LfsRequestBody(String operation, LfsObject[] objects) { + this.operation = operation; + this.objects = objects; + } + + public static LfsRequestBody createUploadRequest(LfsObject object) { + return new LfsRequestBody("upload", new LfsObject[]{object}); + } + + public static LfsRequestBody createDownloadRequest(LfsObject object) { + return new LfsRequestBody("download", new LfsObject[]{object}); + } + + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + private static class LfsResponseBody { + + private LfsObject[] objects; + + public LfsResponseBody() { + } + + public LfsResponseBody(LfsObject[] objects) { + this.objects = objects; + } + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + private static class LfsObject { + + private String oid; + private long size; + private LfsActions actions; + + public LfsObject() { + } + + public LfsObject(String oid, long size) { + this.oid = oid; + this.size = size; + } + + public LfsObject(String oid, long size, LfsActions actions) { + this.oid = oid; + this.size = size; + this.actions = actions; + } + + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + private static class LfsActions { + + private LfsAction upload; + private LfsAction download; + + public LfsActions() { + } + } + + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + private static class LfsAction { + + private String href; + + public LfsAction() { + } + + public LfsAction(String href) { + this.href = href; + } + + } + +} From b51fba22822412e036c06c51b5c27f7540a176ee Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 08:14:04 +0200 Subject: [PATCH 45/56] fix repository browsing with mercurial 4.x --- .hgignore | 1 + scm-plugins/scm-hg-plugin/pom.xml | 2 +- .../resources/sonia/scm/hg/ext/fileview.py | 25 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.hgignore b/.hgignore index a49329d7a5..6ff185f670 100644 --- a/.hgignore +++ b/.hgignore @@ -28,3 +28,4 @@ Desktop DF$ \.idea$ # jrebel rebel.xml +\.pyc diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 170fb089ed..5a69847a7a 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -28,7 +28,7 @@ com.aragost.javahg javahg - 0.7 + 0.8-scm1 com.google.guava diff --git a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py index 907ae5f411..518f229011 100644 --- a/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py +++ b/scm-plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg/ext/fileview.py @@ -32,7 +32,10 @@ Prints date, size and last message of files. """ -from mercurial import util +from mercurial import cmdutil,util + +cmdtable = {} +command = cmdutil.command(cmdtable) class SubRepository: url = None @@ -133,6 +136,14 @@ def printFile(ui, repo, file, disableLastCommit, transport): format = 'f%s\n%i %s %s\0' ui.write( format % (file.path(), file.size(), date, description) ) +@command('fileview', [ + ('r', 'revision', 'tip', 'revision to print'), + ('p', 'path', '', 'path to print'), + ('c', 'recursive', False, 'browse repository recursive'), + ('d', 'disableLastCommit', False, 'disables last commit description and date'), + ('s', 'disableSubRepositoryDetection', False, 'disables detection of sub repositories'), + ('t', 'transport', False, 'format the output for command server'), + ]) def fileview(ui, repo, **opts): files = [] directories = [] @@ -154,15 +165,3 @@ def fileview(ui, repo, **opts): printDirectory(ui, d, transport) for f in files: printFile(ui, repo, f, opts['disableLastCommit'], transport) - -cmdtable = { - # cmd name function call - 'fileview': (fileview,[ - ('r', 'revision', 'tip', 'revision to print'), - ('p', 'path', '', 'path to print'), - ('c', 'recursive', False, 'browse repository recursive'), - ('d', 'disableLastCommit', False, 'disables last commit description and date'), - ('s', 'disableSubRepositoryDetection', False, 'disables detection of sub repositories'), - ('t', 'transport', False, 'format the output for command server'), - ]) -} \ No newline at end of file From 05c2ffbecf289aced6758db4f376632fd45492eb Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 08:15:45 +0200 Subject: [PATCH 46/56] ignore some falsepositive sonarqube issues --- .../main/java/sonia/scm/orientdb/ConnectionConfiguration.java | 1 + .../src/main/java/sonia/scm/orientdb/ConnectionProvider.java | 1 + .../src/main/java/sonia/scm/user/orientdb/UserConverter.java | 1 + scm-plugin-backend/pom.xml | 4 ++++ .../main/java/sonia/scm/plugin/AdminAccountConfiguration.java | 1 + .../main/java/sonia/scm/plugin/security/SecurityModule.java | 1 + .../src/main/java/sonia/scm/sample/auth/SampleConfig.java | 1 + scm-webapp/pom.xml | 4 ++++ 8 files changed, 14 insertions(+) diff --git a/scm-dao-orientdb/src/main/java/sonia/scm/orientdb/ConnectionConfiguration.java b/scm-dao-orientdb/src/main/java/sonia/scm/orientdb/ConnectionConfiguration.java index 8300fd9c5e..e6eae1af0d 100644 --- a/scm-dao-orientdb/src/main/java/sonia/scm/orientdb/ConnectionConfiguration.java +++ b/scm-dao-orientdb/src/main/java/sonia/scm/orientdb/ConnectionConfiguration.java @@ -126,6 +126,7 @@ public class ConnectionConfiguration * @return */ @Override + @SuppressWarnings("squid:S2068") public String toString() { String pwd = null; diff --git a/scm-dao-orientdb/src/main/java/sonia/scm/orientdb/ConnectionProvider.java b/scm-dao-orientdb/src/main/java/sonia/scm/orientdb/ConnectionProvider.java index c1bd0738e4..65f8245f32 100644 --- a/scm-dao-orientdb/src/main/java/sonia/scm/orientdb/ConnectionProvider.java +++ b/scm-dao-orientdb/src/main/java/sonia/scm/orientdb/ConnectionProvider.java @@ -78,6 +78,7 @@ public class ConnectionProvider public static final String DEFAULT_DB_SHEME = "local:"; /** Field description */ + @SuppressWarnings("squid:S2068") public static final String DEFAULT_PASSWORD = "admin"; /** Field description */ diff --git a/scm-dao-orientdb/src/main/java/sonia/scm/user/orientdb/UserConverter.java b/scm-dao-orientdb/src/main/java/sonia/scm/user/orientdb/UserConverter.java index 7aabd57101..9753f21a19 100644 --- a/scm-dao-orientdb/src/main/java/sonia/scm/user/orientdb/UserConverter.java +++ b/scm-dao-orientdb/src/main/java/sonia/scm/user/orientdb/UserConverter.java @@ -76,6 +76,7 @@ public class UserConverter extends AbstractConverter implements Converter public static final String FIELD_MAIL = "mail"; /** Field description */ + @SuppressWarnings("squid:S2068") public static final String FIELD_PASSWORD = "password"; /** Field description */ diff --git a/scm-plugin-backend/pom.xml b/scm-plugin-backend/pom.xml index cd37506db1..91355e5b35 100644 --- a/scm-plugin-backend/pom.xml +++ b/scm-plugin-backend/pom.xml @@ -111,6 +111,10 @@ + + src/main/webapp/template/** + + diff --git a/scm-plugin-backend/src/main/java/sonia/scm/plugin/AdminAccountConfiguration.java b/scm-plugin-backend/src/main/java/sonia/scm/plugin/AdminAccountConfiguration.java index 1b30b1228d..46bbc06c64 100644 --- a/scm-plugin-backend/src/main/java/sonia/scm/plugin/AdminAccountConfiguration.java +++ b/scm-plugin-backend/src/main/java/sonia/scm/plugin/AdminAccountConfiguration.java @@ -133,6 +133,7 @@ public class AdminAccountConfiguration implements SaltedAuthenticationInfo * @return */ @Override + @SuppressWarnings("squid:S2068") public String toString() { //J- diff --git a/scm-plugin-backend/src/main/java/sonia/scm/plugin/security/SecurityModule.java b/scm-plugin-backend/src/main/java/sonia/scm/plugin/security/SecurityModule.java index b902696eef..9be130375e 100644 --- a/scm-plugin-backend/src/main/java/sonia/scm/plugin/security/SecurityModule.java +++ b/scm-plugin-backend/src/main/java/sonia/scm/plugin/security/SecurityModule.java @@ -80,6 +80,7 @@ public class SecurityModule extends ShiroWebModule private static final String PAGE_UNAUTHORIZED = "/error/unauthorized.html"; /** Field description */ + @SuppressWarnings("squid:S2068") private static final String PARAM_PASSWORD = "password"; /** Field description */ diff --git a/scm-samples/scm-sample-auth/src/main/java/sonia/scm/sample/auth/SampleConfig.java b/scm-samples/scm-sample-auth/src/main/java/sonia/scm/sample/auth/SampleConfig.java index 495f65c797..81d8e6b30b 100644 --- a/scm-samples/scm-sample-auth/src/main/java/sonia/scm/sample/auth/SampleConfig.java +++ b/scm-samples/scm-sample-auth/src/main/java/sonia/scm/sample/auth/SampleConfig.java @@ -76,6 +76,7 @@ public class SampleConfig //~--- fields --------------------------------------------------------------- /** Field description */ + @SuppressWarnings("squid:S2068") @XmlElement(name = "password-suffix") private String passwordSuffix = "123"; } diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 0c12b51ed2..735b7ea332 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -542,6 +542,10 @@ 3.0.5 0.8.17 Tomcat + e1 + javascript:S3827 + **.js + src/main/webapp/resources/extjs/**,src/main/webapp/resources/moment/**,src/main/webapp/resources/syntaxhighlighter/** From 6eb480fe847e79e7fd3c935b3af717886e5df22a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 10:41:36 +0200 Subject: [PATCH 47/56] git repository client should return the work tree as working copy instead of .git directory --- .../scm/repository/client/spi/GitRepositoryClientProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java index 2226ad04df..f545540a38 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitRepositoryClientProvider.java @@ -191,7 +191,7 @@ public class GitRepositoryClientProvider extends RepositoryClientProvider @Override public File getWorkingCopy() { - return git.getRepository().getDirectory(); + return git.getRepository().getWorkTree(); } //~--- fields --------------------------------------------------------------- From 58035845cea9e26a32444fac0eee899eea9475b8 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 10:43:29 +0200 Subject: [PATCH 48/56] improve GitRepositoryResolver to allow requests to repositories which ends with .git, the resolver will automatically remove the .git extension and resolves the repository --- .../sonia/scm/web/GitRepositoryResolver.java | 55 +++++---- .../scm/web/GitRepositoryResolverTest.java | 109 ++++++++++++++++++ 2 files changed, 144 insertions(+), 20 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitRepositoryResolverTest.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java index 5ddd000490..2ddf4b3de9 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitRepositoryResolver.java @@ -35,6 +35,7 @@ package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import org.eclipse.jgit.errors.RepositoryNotFoundException; @@ -63,13 +64,11 @@ import javax.servlet.http.HttpServletRequest; * * @author Sebastian Sdorra */ -public class GitRepositoryResolver - implements RepositoryResolver +public class GitRepositoryResolver implements RepositoryResolver { /** the logger for GitRepositoryResolver */ - private static final Logger logger = - LoggerFactory.getLogger(GitRepositoryResolver.class); + private static final Logger logger = LoggerFactory.getLogger(GitRepositoryResolver.class); //~--- constructors --------------------------------------------------------- @@ -114,20 +113,14 @@ public class GitRepositoryResolver if (config.isValid()) { - File gitdir = new File(config.getRepositoryDirectory(), repositoryName); - - if (logger.isDebugEnabled()) - { - logger.debug("try to open git repository at {}", gitdir); - } - - if (!gitdir.exists()) - { + File gitdir = findRepository(config.getRepositoryDirectory(), repositoryName); + if (gitdir == null) { throw new RepositoryNotFoundException(repositoryName); } + + logger.debug("try to open git repository at {}", gitdir); - repository = RepositoryCache.open(FileKey.lenient(gitdir, FS.DETECTED), - true); + repository = RepositoryCache.open(FileKey.lenient(gitdir, FS.DETECTED), true); } else { @@ -139,17 +132,39 @@ public class GitRepositoryResolver throw new ServiceNotEnabledException(); } } - catch (RuntimeException e) - { - throw new RepositoryNotFoundException(repositoryName, e); - } - catch (IOException e) + catch (RuntimeException | IOException e) { throw new RepositoryNotFoundException(repositoryName, e); } return repository; } + + @VisibleForTesting + File findRepository(File parentDirectory, String repositoryName) { + File repositoryDirectory = new File(parentDirectory, repositoryName); + if (repositoryDirectory.exists()) { + return repositoryDirectory; + } + + if (endsWithDotGit(repositoryName)) { + String repositoryNameWithoutDotGit = repositoryNameWithoutDotGit(repositoryName); + repositoryDirectory = new File(parentDirectory, repositoryNameWithoutDotGit); + if (repositoryDirectory.exists()) { + return repositoryDirectory; + } + } + + return null; + } + + private boolean endsWithDotGit(String repositoryName) { + return repositoryName.endsWith(GitRepositoryHandler.DOT_GIT); + } + + private String repositoryNameWithoutDotGit(String repositoryName) { + return repositoryName.substring(0, repositoryName.length() - GitRepositoryHandler.DOT_GIT.length()); + } //~--- fields --------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitRepositoryResolverTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitRepositoryResolverTest.java new file mode 100644 index 0000000000..d41e0acafc --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/web/GitRepositoryResolverTest.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.web; + +import java.io.File; +import java.io.IOException; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; +import sonia.scm.repository.GitConfig; +import sonia.scm.repository.GitRepositoryHandler; + +/** + * Unit tests for {@link GitRepositoryResolver}. + * + * @author Sebastian Sdorra + */ +@RunWith(MockitoJUnitRunner.class) +public class GitRepositoryResolverTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private File parentDirectory; + + @Mock + private GitRepositoryHandler handler; + + @InjectMocks + private GitRepositoryResolver resolver; + + @Before + public void setUp() throws IOException { + parentDirectory = temporaryFolder.newFolder(); + + GitConfig config = new GitConfig(); + config.setRepositoryDirectory(parentDirectory); + + when(handler.getConfig()).thenReturn(config); + } + + @Test + public void testFindRepositoryWithoutDotGit() { + createRepositories("a", "ab"); + + File directory = resolver.findRepository(parentDirectory, "a"); + assertNotNull(directory); + assertEquals("a", directory.getName()); + + directory = resolver.findRepository(parentDirectory, "ab"); + assertNotNull(directory); + assertEquals("ab", directory.getName()); + } + + @Test + public void testFindRepositoryWithDotGit() { + createRepositories("a", "ab"); + + File directory = resolver.findRepository(parentDirectory, "a.git"); + assertNotNull(directory); + assertEquals("a", directory.getName()); + + directory = resolver.findRepository(parentDirectory, "ab.git"); + assertNotNull(directory); + assertEquals("ab", directory.getName()); + } + + private void createRepositories(String... names) { + for (String name : names) { + assertTrue(new File(parentDirectory, name).mkdirs()); + } + } + +} From b7568ea919ca9c4ddc74602a1db11302d350c048 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 10:46:44 +0200 Subject: [PATCH 49/56] introducing new ExtensionPoint for repository path matching The new ExtensionPoint was introduced to remove the tight coupling between the DefaultRepositoryManager and the GitRepositoryHandler. Git has now its own RepositoryPathMatcher which allow the matching of repository with .git or without .git extension. --- .../scm/repository/RepositoryPathMatcher.java | 60 ++++++++ .../repository/GitRepositoryPathMatcher.java | 71 +++++++++ .../GitRepositoryPathMatcherTest.java | 62 ++++++++ .../repository/DefaultRepositoryManager.java | 54 +------ .../scm/repository/RepositoryMatcher.java | 119 +++++++++++++++ .../it/GitRepositoryPathMatcherITCase.java | 143 ++++++++++++++++++ .../DefaultRepositoryManagerPerfTest.java | 4 +- .../DefaultRepositoryManagerTest.java | 40 ++--- .../scm/repository/RepositoryMatcherTest.java | 88 +++++++++++ 9 files changed, 562 insertions(+), 79 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/RepositoryPathMatcher.java create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryPathMatcher.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryPathMatcherTest.java create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/RepositoryMatcher.java create mode 100644 scm-webapp/src/test/java/sonia/scm/it/GitRepositoryPathMatcherITCase.java create mode 100644 scm-webapp/src/test/java/sonia/scm/repository/RepositoryMatcherTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryPathMatcher.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryPathMatcher.java new file mode 100644 index 0000000000..74d8c87f5f --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryPathMatcher.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.repository; + +import sonia.scm.plugin.ExtensionPoint; + +/** + * ExtensionPoint to modify the path matching behaviour for a certain type of repositories. + * + * @author Sebastian Sdorra + * @since 1.54 + */ +@ExtensionPoint +public interface RepositoryPathMatcher { + + /** + * Returns {@code true} if the path matches the repository. + * + * @param repository repository + * @param path requested path without context and without type information extracted from uri + * + * @return {@code true} if the path matches + */ + boolean isPathMatching(Repository repository, String path); + + /** + * Returns the type of repository for which the matcher is responsible. + * + * @return type of repository + */ + String getType(); +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryPathMatcher.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryPathMatcher.java new file mode 100644 index 0000000000..e77ac03da1 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryPathMatcher.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.repository; + +import sonia.scm.plugin.ext.Extension; +import sonia.scm.util.HttpUtil; +import sonia.scm.util.Util; + +/** + * Matches git repositories with ".git" and without ".git". + * + * @author Sebastian Sdorra + * @since 1.54 + */ +@Extension +public class GitRepositoryPathMatcher implements RepositoryPathMatcher { + + @Override + public boolean isPathMatching(Repository repository, String path) { + String repositoryName = repository.getName(); + + if (path.startsWith(repositoryName)) { + + String pathPart = path.substring(repositoryName.length()); + + // git repository may also be named <>.git by convention + if (pathPart.startsWith(GitRepositoryHandler.DOT_GIT)) { + // if this is the case, just also cut it away + pathPart = pathPart.substring(GitRepositoryHandler.DOT_GIT.length()); + } + + return Util.isEmpty(pathPart) || pathPart.startsWith(HttpUtil.SEPARATOR_PATH); + } + + return false; + } + + @Override + public String getType() { + return GitRepositoryHandler.TYPE_NAME; + } + +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryPathMatcherTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryPathMatcherTest.java new file mode 100644 index 0000000000..7adc4a6913 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitRepositoryPathMatcherTest.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.repository; + +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Unit tests for {@link GitRepositoryPathMatcher}. + * + * @author Sebastian Sdorra + * @since 1.54 + */ +public class GitRepositoryPathMatcherTest { + + private final GitRepositoryPathMatcher pathMatcher = new GitRepositoryPathMatcher(); + + @Test + public void testIsPathMatching() { + assertFalse(pathMatcher.isPathMatching(repository("my-repo"), "my-repoo")); + assertFalse(pathMatcher.isPathMatching(repository("my"), "my-repo")); + assertFalse(pathMatcher.isPathMatching(repository("my"), "my-repo/with/path")); + + assertTrue(pathMatcher.isPathMatching(repository("my-repo"), "my-repo")); + assertTrue(pathMatcher.isPathMatching(repository("my-repo"), "my-repo.git")); + assertTrue(pathMatcher.isPathMatching(repository("my-repo"), "my-repo/with/path")); + assertTrue(pathMatcher.isPathMatching(repository("my-repo"), "my-repo.git/with/path")); + } + + private Repository repository(String name) { + return new Repository(name, GitRepositoryHandler.TYPE_NAME, name); + } + +} 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 5369ee1c11..1c40313f97 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -35,7 +35,6 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -110,6 +109,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager * @param repositoryListenersProvider * @param repositoryHooksProvider * @param preProcessorUtil + * @param repositoryMatcher */ @Inject public DefaultRepositoryManager(ScmConfiguration configuration, @@ -117,7 +117,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager RepositoryDAO repositoryDAO, Set handlerSet, Provider> repositoryListenersProvider, Provider> repositoryHooksProvider, - PreProcessorUtil preProcessorUtil) + PreProcessorUtil preProcessorUtil, RepositoryMatcher repositoryMatcher) { this.configuration = configuration; this.keyGenerator = keyGenerator; @@ -125,6 +125,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager this.repositoryListenersProvider = repositoryListenersProvider; this.repositoryHooksProvider = repositoryHooksProvider; this.preProcessorUtil = preProcessorUtil; + this.repositoryMatcher = repositoryMatcher; //J- ThreadFactory factory = new ThreadFactoryBuilder() @@ -703,7 +704,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager for (Repository r : repositories) { - if (type.equals(r.getType()) && isNameMatching(r, uri)) + if (repositoryMatcher.matches(r, type, uri)) { assertIsReader(r); repository = r.clone(); @@ -972,51 +973,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager return handler; } - /** - * This method checks whether or not the provided path belongs to the provided repository. - * - * @param repository The repository to be tested. - * @param path The path that might be part of the repository. - * @return Returns true if path belongs to the repository. Returns false otherwise. - */ - private boolean isNameMatching(Repository repository, String path) { - return isNameMatching(repository.getType(), repository.getName(), path); - } - - /** - * This method checks whether or not the provided path belongs to the provided repository. - * - * @param repositoryType The type of the repository being tested. - * @param repositoryName The name of the repository being tested. - * @param path The path that might be part of the repository. - * @return Returns true if path belongs to the repository. Returns false otherwise. - */ - @VisibleForTesting - boolean isNameMatching(String repositoryType, String repositoryName, String path) { - boolean result = false; - - if (path.startsWith(repositoryName)) { - - String pathPart = path.substring(repositoryName.length()); - - //TODO: this introduces a strong coupling to the git plugin. This can be resolved with a "Repository Matcher" API. - //ausformulieren, ticketId weg - if (GitRepositoryHandler.TYPE_NAME.equals(repositoryType)) { - - //git repository may also be named <>.git by convention - if (pathPart.startsWith(GitRepositoryHandler.DOT_GIT)) { - //if this is the case, just also cut it away - pathPart = pathPart.substring(GitRepositoryHandler.DOT_GIT.length()); - } - } - - result = Util.isEmpty(pathPart) || pathPart.startsWith(HttpUtil.SEPARATOR_PATH); - - } - - return result; - } - /** * Method description * @@ -1072,4 +1028,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager /** Field description */ private Set types; + + private RepositoryMatcher repositoryMatcher; } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/RepositoryMatcher.java b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryMatcher.java new file mode 100644 index 0000000000..d0ad30e84f --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/RepositoryMatcher.java @@ -0,0 +1,119 @@ + /** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + +package sonia.scm.repository; + +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Set; +import javax.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.util.HttpUtil; +import sonia.scm.util.Util; + +/** + * RepositoryMatcher is able to check if a repository matches the requested path. + * + * @author Sebastian Sdorra + * @since 1.54 + */ +public final class RepositoryMatcher { + + private static final Logger LOG = LoggerFactory.getLogger(RepositoryMatcher.class); + + private static final RepositoryPathMatcher DEFAULT_PATH_MATCHER = new DefaultRepositoryPathMatcher(); + + private final Map pathMatchers; + + /** + * Creates a new instance. + * + * @param pathMatchers injected set of {@link RepositoryPathMatcher}. + */ + @Inject + public RepositoryMatcher(Set pathMatchers) { + this.pathMatchers = Maps.newHashMap(); + for ( RepositoryPathMatcher pathMatcher : pathMatchers ) { + LOG.info("register custom repository path matcher for type {}", pathMatcher.getType()); + this.pathMatchers.put(pathMatcher.getType(), pathMatcher); + } + } + + /** + * Returns {@code true} is the repository matches the type and the name matches the requested path. + * + * @param repository repository + * @param type type of repository + * @param path requested path without context and without type information + * + * @return {@code true} is the repository matches + */ + public boolean matches(Repository repository, String type, String path) { + return type.equals(repository.getType()) && isPathMatching(repository, path); + } + + private boolean isPathMatching(Repository repository, String path) { + return getPathMatcherForType(repository.getType()).isPathMatching(repository, path); + } + + private RepositoryPathMatcher getPathMatcherForType(String type) { + RepositoryPathMatcher pathMatcher = pathMatchers.get(type); + if (pathMatcher == null) { + pathMatcher = DEFAULT_PATH_MATCHER; + } + return pathMatcher; + } + + private static class DefaultRepositoryPathMatcher implements RepositoryPathMatcher { + + @Override + public boolean isPathMatching(Repository repository, String path) { + String name = repository.getName(); + + if (path.startsWith(name)) { + String sub = path.substring(name.length()); + + return Util.isEmpty(sub) || sub.startsWith(HttpUtil.SEPARATOR_PATH); + } + + return false; + } + + @Override + public String getType() { + return "any"; + } + + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/it/GitRepositoryPathMatcherITCase.java b/scm-webapp/src/test/java/sonia/scm/it/GitRepositoryPathMatcherITCase.java new file mode 100644 index 0000000000..079e2c520e --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/it/GitRepositoryPathMatcherITCase.java @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.it; + +import com.google.common.base.Charsets; +import com.google.common.io.Files; +import com.sun.jersey.api.client.Client; +import java.io.File; +import java.io.IOException; +import org.junit.After; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import static sonia.scm.it.IntegrationTestUtil.*; +import static sonia.scm.it.RepositoryITUtil.*; +import sonia.scm.repository.Person; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryTestData; +import sonia.scm.repository.client.api.ClientCommand; +import sonia.scm.repository.client.api.RepositoryClient; +import sonia.scm.repository.client.api.RepositoryClientFactory; + +/** + * Integration test for RepositoryPathMatching with ".git" and without ".git". + * + * @author Sebastian Sdorra + * @since 1.54 + */ +public class GitRepositoryPathMatcherITCase { + + private static final RepositoryClientFactory REPOSITORY_CLIENT_FACTORY = new RepositoryClientFactory(); + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private Client apiClient; + private Repository repository; + + @Before + public void setUp() { + apiClient = createAdminClient(); + Repository testRepository = RepositoryTestData.createHeartOfGold("git"); + this.repository = createRepository(apiClient, testRepository); + } + + @After + public void tearDown() { + deleteRepository(apiClient, repository.getId()); + } + + // tests begin + + @Test + public void testWithoutDotGit() throws IOException { + String urlWithoutDotGit = createUrl(); + cloneAndPush(urlWithoutDotGit); + } + + @Test + public void testWithDotGit() throws IOException { + String urlWithDotGit = createUrl() + ".git"; + cloneAndPush(urlWithDotGit); + } + + // tests end + + private String createUrl() { + return BASE_URL + "git/" + repository.getName(); + } + + private void cloneAndPush( String url ) throws IOException { + cloneRepositoryAndPushFiles(url); + cloneRepositoryAndCheckFiles(url); + } + + private void cloneRepositoryAndPushFiles(String url) throws IOException { + RepositoryClient repositoryClient = createRepositoryClient(url); + + Files.write("a", new File(repositoryClient.getWorkingCopy(), "a.txt"), Charsets.UTF_8); + repositoryClient.getAddCommand().add("a.txt"); + commit(repositoryClient, "added a"); + + Files.write("b", new File(repositoryClient.getWorkingCopy(), "b.txt"), Charsets.UTF_8); + repositoryClient.getAddCommand().add("b.txt"); + commit(repositoryClient, "added b"); + } + + private void cloneRepositoryAndCheckFiles(String url) throws IOException { + RepositoryClient repositoryClient = createRepositoryClient(url); + File workingCopy = repositoryClient.getWorkingCopy(); + + File a = new File(workingCopy, "a.txt"); + assertTrue(a.exists()); + assertEquals("a", Files.toString(a, Charsets.UTF_8)); + + File b = new File(workingCopy, "b.txt"); + assertTrue(b.exists()); + assertEquals("b", Files.toString(b, Charsets.UTF_8)); + } + + private void commit(RepositoryClient repositoryClient, String message) throws IOException { + repositoryClient.getCommitCommand().commit( + new Person("scmadmin", "scmadmin@scm-manager.org"), message + ); + if ( repositoryClient.isCommandSupported(ClientCommand.PUSH) ) { + repositoryClient.getPushCommand().push(); + } + } + + private RepositoryClient createRepositoryClient(String url) throws IOException { + return REPOSITORY_CLIENT_FACTORY.create("git", url, ADMIN_USERNAME, ADMIN_PASSWORD, tempFolder.newFolder()); + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java index e00b2ccd6d..56b688099c 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerPerfTest.java @@ -117,6 +117,7 @@ public class DefaultRepositoryManagerPerfTest { Set handlerSet = ImmutableSet.of(repositoryHandler); Provider> repositoryListenersProvider = new SetProvider(); Provider> repositoryHooksProvider = new SetProvider(); + RepositoryMatcher repositoryMatcher = new RepositoryMatcher(Collections.emptySet()); repositoryManager = new DefaultRepositoryManager( configuration, @@ -126,7 +127,8 @@ public class DefaultRepositoryManagerPerfTest { handlerSet, repositoryListenersProvider, repositoryHooksProvider, - preProcessorUtil + preProcessorUtil, + repositoryMatcher ); setUpTestRepositories(); 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 7b7c304230..28b8613b7b 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -46,7 +46,6 @@ import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.store.JAXBStoreFactory; import sonia.scm.store.StoreFactory; -import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -54,6 +53,7 @@ import static org.mockito.Mockito.*; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -77,45 +77,21 @@ public class DefaultRepositoryManagerTest extends RepositoryManagerTestBase throws RepositoryException, IOException { RepositoryManager m = createManager(); - m.init(contextProvider); + createRepository(m, new Repository("1", "hg", "scm")); createRepository(m, new Repository("2", "hg", "scm-test")); createRepository(m, new Repository("3", "git", "project1/test-1")); createRepository(m, new Repository("4", "git", "project1/test-2")); + assertEquals("scm", m.getFromUri("hg/scm").getName()); assertEquals("scm-test", m.getFromUri("hg/scm-test").getName()); assertEquals("scm-test", m.getFromUri("/hg/scm-test").getName()); - assertEquals("project1/test-1", - m.getFromUri("/git/project1/test-1").getName()); - assertEquals("project1/test-1", - m.getFromUri("/git/project1/test-1/ka/some/path").getName()); + assertEquals("project1/test-1", m.getFromUri("/git/project1/test-1").getName()); + assertEquals("project1/test-1", m.getFromUri("/git/project1/test-1/ka/some/path").getName()); assertNull(m.getFromUri("/git/project1/test-3/ka/some/path")); } - @Test - public void testNameIsMatching() throws Exception { - DefaultRepositoryManager m = createManager(); - - assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-name"), is(true)); - assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-name/"), is(true)); - assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-name/and-more-is-valid"), is(true)); - assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-name.git/and-more-is-valid"), - is(true)); - - - assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "not-the-name"), is(false)); - assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "repo-na"), is(false)); - - assertThat(m.isNameMatching(GitRepositoryHandler.TYPE_NAME, "repo-name", "/repo-name/"), is(false)); - - assertThat(m.isNameMatching(HgRepositoryHandler.TYPE_NAME, "repo-name", "repo-name.git/and-more-is-valid"), - is(false)); - assertThat(m.isNameMatching(SvnRepositoryHandler.TYPE_NAME, "repo-name", "repo-name.git/and-more-is-valid"), - is(false)); - - } - //~--- methods -------------------------------------------------------------- /** @@ -180,7 +156,7 @@ public class DefaultRepositoryManagerTest extends RepositoryManagerTestBase return new DefaultRepositoryManager(configuration, contextProvider, new DefaultKeyGenerator(), repositoryDAO, handlerSet, listenerProvider, - hookProvider, createEmptyPreProcessorUtil()); + hookProvider, createEmptyPreProcessorUtil(), createRepositoryMatcher()); } /** @@ -202,6 +178,10 @@ public class DefaultRepositoryManagerTest extends RepositoryManagerTestBase ); //J+ } + + private RepositoryMatcher createRepositoryMatcher() { + return new RepositoryMatcher(Collections.emptySet()); + } /** * Method description diff --git a/scm-webapp/src/test/java/sonia/scm/repository/RepositoryMatcherTest.java b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryMatcherTest.java new file mode 100644 index 0000000000..c832bb8691 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/repository/RepositoryMatcherTest.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.repository; + +import com.google.common.collect.Sets; +import java.util.Set; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Before; + +/** + * Unit tests for {@link RepositoryMatcher}. + * + * @author Sebastian Sdorra + * @since 1.54 + */ +public class RepositoryMatcherTest { + + private RepositoryMatcher matcher; + + @Before + public void setUp() { + Set pathMatchers = Sets.newHashSet(new AbcRepositoryPathMatcher()); + this.matcher = new RepositoryMatcher(pathMatchers); + } + + @Test + public void testMatches() { + assertFalse(matcher.matches(repository("hg", "scm"), "hg", "scm-test/ka")); + assertFalse(matcher.matches(repository("git", "scm-test"), "hg", "scm-test")); + + assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "scm-test/ka")); + assertTrue(matcher.matches(repository("hg", "scm-test"), "hg", "scm-test")); + } + + @Test + public void testMatchesWithCustomPathMatcher() { + assertFalse(matcher.matches(repository("abc", "scm"), "hg", "/long/path/with/abc")); + assertTrue(matcher.matches(repository("abc", "scm"), "abc", "/long/path/with/abc")); + } + + private Repository repository(String type, String name) { + return new Repository(type + "-" + name, type, name); + } + + private static class AbcRepositoryPathMatcher implements RepositoryPathMatcher { + + @Override + public boolean isPathMatching(Repository repository, String path) { + return path.endsWith("abc"); + } + + @Override + public String getType() { + return "abc"; + } + + } + +} From 2df42711dc833cf711a58e4d11430508252bb177 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 11:26:22 +0200 Subject: [PATCH 50/56] update jersey to version 1.19.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5b0b532bbd..8996ca46e2 100644 --- a/pom.xml +++ b/pom.xml @@ -414,7 +414,7 @@ 1.1.10 2.5 3.0 - 1.19.3 + 1.19.4 2.6.6 2.3.20 7.6.19.v20160209 From 0b78c4eeebbaad8f407f88b5dab531d41a756a0f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 11:21:05 +0200 Subject: [PATCH 51/56] fix wrong itcase commit message --- scm-webapp/src/test/java/sonia/scm/it/RepositoryHookITCase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/test/java/sonia/scm/it/RepositoryHookITCase.java b/scm-webapp/src/test/java/sonia/scm/it/RepositoryHookITCase.java index 4f3af7ecd7..b68e7d543f 100644 --- a/scm-webapp/src/test/java/sonia/scm/it/RepositoryHookITCase.java +++ b/scm-webapp/src/test/java/sonia/scm/it/RepositoryHookITCase.java @@ -179,7 +179,7 @@ public class RepositoryHookITCase extends AbstractAdminITCaseBase private Changeset commit(String message) throws IOException { Changeset a = repositoryClient.getCommitCommand().commit( - new Person("scmadmin", "scmadmin@scm-manager.org"), "added a" + new Person("scmadmin", "scmadmin@scm-manager.org"), message ); if ( repositoryClient.isCommandSupported(ClientCommand.PUSH) ) { repositoryClient.getPushCommand().push(); From 663b1d9248e91d5a1bbdc712bd3f1e9519b6095c Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 11:21:43 +0200 Subject: [PATCH 52/56] update jetty to version 7.6.21.v20160908 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8996ca46e2..9084e39719 100644 --- a/pom.xml +++ b/pom.xml @@ -417,7 +417,7 @@ 1.19.4 2.6.6 2.3.20 - 7.6.19.v20160209 + 7.6.21.v20160908 7.6.16.v20140903 From fd02f4c068864503304887f68c0545def918f1e1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 11:39:34 +0200 Subject: [PATCH 53/56] [maven-release-plugin] prepare release 1.54 --- maven/pom.xml | 4 ++-- maven/scm-maven-plugin/pom.xml | 4 ++-- maven/scm-plugin-archetype/pom.xml | 4 ++-- pom.xml | 4 ++-- scm-clients/pom.xml | 6 +++--- scm-clients/scm-cli-client/pom.xml | 6 +++--- scm-clients/scm-client-api/pom.xml | 4 ++-- scm-clients/scm-client-impl/pom.xml | 8 ++++---- scm-core/pom.xml | 4 ++-- scm-dao-orientdb/pom.xml | 8 ++++---- scm-dao-xml/pom.xml | 8 ++++---- scm-plugin-backend/pom.xml | 6 +++--- scm-plugins/pom.xml | 8 ++++---- scm-plugins/scm-git-plugin/pom.xml | 6 +++--- scm-plugins/scm-hg-plugin/pom.xml | 6 +++--- scm-plugins/scm-svn-plugin/pom.xml | 6 +++--- scm-samples/pom.xml | 4 ++-- scm-samples/scm-sample-auth/pom.xml | 6 +++--- scm-samples/scm-sample-hello/pom.xml | 6 +++--- scm-server/pom.xml | 4 ++-- scm-test/pom.xml | 6 +++--- scm-webapp/pom.xml | 24 ++++++++++++------------ support/pom.xml | 4 ++-- support/scm-support-btrace/pom.xml | 6 +++--- 24 files changed, 76 insertions(+), 76 deletions(-) diff --git a/maven/pom.xml b/maven/pom.xml index d928f0515c..d19c43f519 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54-SNAPSHOT + 1.54 sonia.scm.maven scm-maven-plugins pom - 1.54-SNAPSHOT + 1.54 scm-maven-plugins diff --git a/maven/scm-maven-plugin/pom.xml b/maven/scm-maven-plugin/pom.xml index f038c904ab..a4ff08d51f 100644 --- a/maven/scm-maven-plugin/pom.xml +++ b/maven/scm-maven-plugin/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.54-SNAPSHOT + 1.54 sonia.scm.maven scm-maven-plugin - 1.54-SNAPSHOT + 1.54 maven-plugin scm-maven-plugin diff --git a/maven/scm-plugin-archetype/pom.xml b/maven/scm-plugin-archetype/pom.xml index 9aec6b5ac3..6e5ee29329 100644 --- a/maven/scm-plugin-archetype/pom.xml +++ b/maven/scm-plugin-archetype/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.54-SNAPSHOT + 1.54 sonia.scm.maven scm-plugin-archetype - 1.54-SNAPSHOT + 1.54 scm-plugin-archetype diff --git a/pom.xml b/pom.xml index 9084e39719..52e006e696 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ sonia.scm scm pom - 1.54-SNAPSHOT + 1.54 The easiest way to share your Git, Mercurial and Subversion repositories over http. @@ -36,7 +36,7 @@ scm:hg:http://bitbucket.org/sdorra/scm-manager scm:hg:https://bitbucket.org/sdorra/scm-manager http://bitbucket.org/sdorra/scm-manager - HEAD + 1.54 diff --git a/scm-clients/pom.xml b/scm-clients/pom.xml index dc6b8015c7..9491a7033f 100644 --- a/scm-clients/pom.xml +++ b/scm-clients/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54-SNAPSHOT + 1.54 sonia.scm.clients scm-clients pom - 1.54-SNAPSHOT + 1.54 scm-clients @@ -32,7 +32,7 @@ scm-core sonia.scm jar - 1.54-SNAPSHOT + 1.54 shiro-core diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml index 340d3c0260..5521ee7717 100644 --- a/scm-clients/scm-cli-client/pom.xml +++ b/scm-clients/scm-cli-client/pom.xml @@ -6,12 +6,12 @@ scm-clients sonia.scm.clients - 1.54-SNAPSHOT + 1.54 sonia.scm.clients scm-cli-client - 1.54-SNAPSHOT + 1.54 scm-cli-client @@ -34,7 +34,7 @@ sonia.scm.clients scm-client-impl - 1.54-SNAPSHOT + 1.54 diff --git a/scm-clients/scm-client-api/pom.xml b/scm-clients/scm-client-api/pom.xml index 0e31381fc5..18761ab4aa 100644 --- a/scm-clients/scm-client-api/pom.xml +++ b/scm-clients/scm-client-api/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.54-SNAPSHOT + 1.54 sonia.scm.clients scm-client-api jar - 1.54-SNAPSHOT + 1.54 scm-client-api diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml index 414cc661a7..ca69406675 100644 --- a/scm-clients/scm-client-impl/pom.xml +++ b/scm-clients/scm-client-impl/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.54-SNAPSHOT + 1.54 sonia.scm.clients scm-client-impl jar - 1.54-SNAPSHOT + 1.54 scm-client-impl @@ -36,7 +36,7 @@ sonia.scm.clients scm-client-api - 1.54-SNAPSHOT + 1.54 @@ -70,7 +70,7 @@ sonia.scm scm-test - 1.54-SNAPSHOT + 1.54 test diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 59f89d6f4a..cdd70804a1 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.54-SNAPSHOT + 1.54 sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 scm-core diff --git a/scm-dao-orientdb/pom.xml b/scm-dao-orientdb/pom.xml index 3af411018f..e2ecd42125 100644 --- a/scm-dao-orientdb/pom.xml +++ b/scm-dao-orientdb/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.54-SNAPSHOT + 1.54 sonia.scm scm-dao-orientdb - 1.54-SNAPSHOT + 1.54 scm-dao-orientdb @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 @@ -52,7 +52,7 @@ sonia.scm scm-test - 1.54-SNAPSHOT + 1.54 test diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index 2732e02890..a2b19ab949 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.54-SNAPSHOT + 1.54 sonia.scm scm-dao-xml - 1.54-SNAPSHOT + 1.54 scm-dao-xml @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 @@ -34,7 +34,7 @@ sonia.scm scm-test - 1.54-SNAPSHOT + 1.54 test diff --git a/scm-plugin-backend/pom.xml b/scm-plugin-backend/pom.xml index 91355e5b35..3dc928f370 100644 --- a/scm-plugin-backend/pom.xml +++ b/scm-plugin-backend/pom.xml @@ -6,13 +6,13 @@ scm sonia.scm - 1.54-SNAPSHOT + 1.54 sonia.scm scm-plugin-backend war - 1.54-SNAPSHOT + 1.54 ${project.artifactId} @@ -62,7 +62,7 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index f89e1c5512..4a755d83b6 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54-SNAPSHOT + 1.54 sonia.scm.plugins scm-plugins pom - 1.54-SNAPSHOT + 1.54 scm-plugins @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 @@ -59,7 +59,7 @@ sonia.scm.maven scm-maven-plugin - 1.54-SNAPSHOT + 1.54 process-resources diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 4782bce9fe..205ee00760 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.54-SNAPSHOT + 1.54 sonia.scm.plugins scm-git-plugin - 1.54-SNAPSHOT + 1.54 scm-git-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Git @@ -54,7 +54,7 @@ sonia.scm scm-test - 1.54-SNAPSHOT + 1.54 test diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 5a69847a7a..4133064da1 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -6,12 +6,12 @@ sonia.scm.plugins scm-plugins - 1.54-SNAPSHOT + 1.54 sonia.scm.plugins scm-hg-plugin - 1.54-SNAPSHOT + 1.54 scm-hg-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Mercurial @@ -42,7 +42,7 @@ sonia.scm scm-test - 1.54-SNAPSHOT + 1.54 test diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index cffc66d9b2..3cc04eecbc 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.54-SNAPSHOT + 1.54 sonia.scm.plugins scm-svn-plugin - 1.54-SNAPSHOT + 1.54 scm-svn-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Subversion @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.54-SNAPSHOT + 1.54 test diff --git a/scm-samples/pom.xml b/scm-samples/pom.xml index 5000e2e762..8d1ad94701 100644 --- a/scm-samples/pom.xml +++ b/scm-samples/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54-SNAPSHOT + 1.54 sonia.scm.samples scm-samples pom - 1.54-SNAPSHOT + 1.54 scm-samples diff --git a/scm-samples/scm-sample-auth/pom.xml b/scm-samples/scm-sample-auth/pom.xml index 3d95159d88..6992651de1 100644 --- a/scm-samples/scm-sample-auth/pom.xml +++ b/scm-samples/scm-sample-auth/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.54-SNAPSHOT + 1.54 sonia.scm.sample scm-sample-auth - 1.54-SNAPSHOT + 1.54 scm-sample-auth Sample Authentication Plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 diff --git a/scm-samples/scm-sample-hello/pom.xml b/scm-samples/scm-sample-hello/pom.xml index ca98c15641..8e142b86b8 100644 --- a/scm-samples/scm-sample-hello/pom.xml +++ b/scm-samples/scm-sample-hello/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.54-SNAPSHOT + 1.54 sonia.scm.sample scm-sample-hello - 1.54-SNAPSHOT + 1.54 scm-sample-hello A simple hello world plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 0ce9c9d923..e6756b9edd 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.54-SNAPSHOT + 1.54 sonia.scm scm-server - 1.54-SNAPSHOT + 1.54 scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index a7595788ec..6b4f34760f 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.54-SNAPSHOT + 1.54 sonia.scm scm-test - 1.54-SNAPSHOT + 1.54 scm-test @@ -25,7 +25,7 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 735b7ea332..0e62ad77cf 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54-SNAPSHOT + 1.54 sonia.scm scm-webapp war - 1.54-SNAPSHOT + 1.54 scm-webapp @@ -38,31 +38,31 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 sonia.scm scm-dao-xml - 1.54-SNAPSHOT + 1.54 sonia.scm.plugins scm-hg-plugin - 1.54-SNAPSHOT + 1.54 sonia.scm.plugins scm-svn-plugin - 1.54-SNAPSHOT + 1.54 sonia.scm.plugins scm-git-plugin - 1.54-SNAPSHOT + 1.54 @@ -285,7 +285,7 @@ sonia.scm scm-test - 1.54-SNAPSHOT + 1.54 test @@ -298,7 +298,7 @@ sonia.scm.plugins scm-git-plugin - 1.54-SNAPSHOT + 1.54 tests test @@ -306,7 +306,7 @@ sonia.scm.plugins scm-hg-plugin - 1.54-SNAPSHOT + 1.54 tests test @@ -314,7 +314,7 @@ sonia.scm.plugins scm-svn-plugin - 1.54-SNAPSHOT + 1.54 tests test @@ -558,7 +558,7 @@ sonia.scm scm-dao-orientdb - 1.54-SNAPSHOT + 1.54 diff --git a/support/pom.xml b/support/pom.xml index 091bded8a7..43559ba521 100644 --- a/support/pom.xml +++ b/support/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54-SNAPSHOT + 1.54 sonia.scm.support scm-support pom - 1.54-SNAPSHOT + 1.54 scm-support diff --git a/support/scm-support-btrace/pom.xml b/support/scm-support-btrace/pom.xml index 698824670d..98e16acdc6 100644 --- a/support/scm-support-btrace/pom.xml +++ b/support/scm-support-btrace/pom.xml @@ -4,12 +4,12 @@ sonia.scm.support scm-support - 1.54-SNAPSHOT + 1.54 sonia.scm scm-support-btrace - 1.54-SNAPSHOT + 1.54 jar scm-support-btrace @@ -18,7 +18,7 @@ sonia.scm scm-core - 1.54-SNAPSHOT + 1.54 From a7366731baa49cd03b9580c5c7a0424314424dfc Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 11:39:34 +0200 Subject: [PATCH 54/56] [maven-release-plugin] copy for tag 1.54 From 07a1c8b15194a11056b7fe14d6a3ad865793f54b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 6 Jun 2017 11:39:35 +0200 Subject: [PATCH 55/56] [maven-release-plugin] prepare for next development iteration --- maven/pom.xml | 4 ++-- maven/scm-maven-plugin/pom.xml | 4 ++-- maven/scm-plugin-archetype/pom.xml | 4 ++-- pom.xml | 4 ++-- scm-clients/pom.xml | 6 +++--- scm-clients/scm-cli-client/pom.xml | 6 +++--- scm-clients/scm-client-api/pom.xml | 4 ++-- scm-clients/scm-client-impl/pom.xml | 8 ++++---- scm-core/pom.xml | 4 ++-- scm-dao-orientdb/pom.xml | 8 ++++---- scm-dao-xml/pom.xml | 8 ++++---- scm-plugin-backend/pom.xml | 6 +++--- scm-plugins/pom.xml | 8 ++++---- scm-plugins/scm-git-plugin/pom.xml | 6 +++--- scm-plugins/scm-hg-plugin/pom.xml | 6 +++--- scm-plugins/scm-svn-plugin/pom.xml | 6 +++--- scm-samples/pom.xml | 4 ++-- scm-samples/scm-sample-auth/pom.xml | 6 +++--- scm-samples/scm-sample-hello/pom.xml | 6 +++--- scm-server/pom.xml | 4 ++-- scm-test/pom.xml | 6 +++--- scm-webapp/pom.xml | 24 ++++++++++++------------ support/pom.xml | 4 ++-- support/scm-support-btrace/pom.xml | 6 +++--- 24 files changed, 76 insertions(+), 76 deletions(-) diff --git a/maven/pom.xml b/maven/pom.xml index d19c43f519..86be4ebaed 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54 + 1.55-SNAPSHOT sonia.scm.maven scm-maven-plugins pom - 1.54 + 1.55-SNAPSHOT scm-maven-plugins diff --git a/maven/scm-maven-plugin/pom.xml b/maven/scm-maven-plugin/pom.xml index a4ff08d51f..da0c4192f0 100644 --- a/maven/scm-maven-plugin/pom.xml +++ b/maven/scm-maven-plugin/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.54 + 1.55-SNAPSHOT sonia.scm.maven scm-maven-plugin - 1.54 + 1.55-SNAPSHOT maven-plugin scm-maven-plugin diff --git a/maven/scm-plugin-archetype/pom.xml b/maven/scm-plugin-archetype/pom.xml index 6e5ee29329..61cec4e20b 100644 --- a/maven/scm-plugin-archetype/pom.xml +++ b/maven/scm-plugin-archetype/pom.xml @@ -6,12 +6,12 @@ scm-maven-plugins sonia.scm.maven - 1.54 + 1.55-SNAPSHOT sonia.scm.maven scm-plugin-archetype - 1.54 + 1.55-SNAPSHOT scm-plugin-archetype diff --git a/pom.xml b/pom.xml index 52e006e696..e31fc9c77a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ sonia.scm scm pom - 1.54 + 1.55-SNAPSHOT The easiest way to share your Git, Mercurial and Subversion repositories over http. @@ -36,7 +36,7 @@ scm:hg:http://bitbucket.org/sdorra/scm-manager scm:hg:https://bitbucket.org/sdorra/scm-manager http://bitbucket.org/sdorra/scm-manager - 1.54 + HEAD diff --git a/scm-clients/pom.xml b/scm-clients/pom.xml index 9491a7033f..d5264575e7 100644 --- a/scm-clients/pom.xml +++ b/scm-clients/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54 + 1.55-SNAPSHOT sonia.scm.clients scm-clients pom - 1.54 + 1.55-SNAPSHOT scm-clients @@ -32,7 +32,7 @@ scm-core sonia.scm jar - 1.54 + 1.55-SNAPSHOT shiro-core diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml index 5521ee7717..77a2be54a6 100644 --- a/scm-clients/scm-cli-client/pom.xml +++ b/scm-clients/scm-cli-client/pom.xml @@ -6,12 +6,12 @@ scm-clients sonia.scm.clients - 1.54 + 1.55-SNAPSHOT sonia.scm.clients scm-cli-client - 1.54 + 1.55-SNAPSHOT scm-cli-client @@ -34,7 +34,7 @@ sonia.scm.clients scm-client-impl - 1.54 + 1.55-SNAPSHOT diff --git a/scm-clients/scm-client-api/pom.xml b/scm-clients/scm-client-api/pom.xml index 18761ab4aa..e002b8ff75 100644 --- a/scm-clients/scm-client-api/pom.xml +++ b/scm-clients/scm-client-api/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.54 + 1.55-SNAPSHOT sonia.scm.clients scm-client-api jar - 1.54 + 1.55-SNAPSHOT scm-client-api diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml index ca69406675..255232bc7b 100644 --- a/scm-clients/scm-client-impl/pom.xml +++ b/scm-clients/scm-client-impl/pom.xml @@ -6,13 +6,13 @@ sonia.scm.clients scm-clients - 1.54 + 1.55-SNAPSHOT sonia.scm.clients scm-client-impl jar - 1.54 + 1.55-SNAPSHOT scm-client-impl @@ -36,7 +36,7 @@ sonia.scm.clients scm-client-api - 1.54 + 1.55-SNAPSHOT @@ -70,7 +70,7 @@ sonia.scm scm-test - 1.54 + 1.55-SNAPSHOT test diff --git a/scm-core/pom.xml b/scm-core/pom.xml index cdd70804a1..3496d9ab01 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.54 + 1.55-SNAPSHOT sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT scm-core diff --git a/scm-dao-orientdb/pom.xml b/scm-dao-orientdb/pom.xml index e2ecd42125..e89bb78811 100644 --- a/scm-dao-orientdb/pom.xml +++ b/scm-dao-orientdb/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.54 + 1.55-SNAPSHOT sonia.scm scm-dao-orientdb - 1.54 + 1.55-SNAPSHOT scm-dao-orientdb @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT @@ -52,7 +52,7 @@ sonia.scm scm-test - 1.54 + 1.55-SNAPSHOT test diff --git a/scm-dao-xml/pom.xml b/scm-dao-xml/pom.xml index a2b19ab949..79716b8b28 100644 --- a/scm-dao-xml/pom.xml +++ b/scm-dao-xml/pom.xml @@ -6,12 +6,12 @@ sonia.scm scm - 1.54 + 1.55-SNAPSHOT sonia.scm scm-dao-xml - 1.54 + 1.55-SNAPSHOT scm-dao-xml @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT @@ -34,7 +34,7 @@ sonia.scm scm-test - 1.54 + 1.55-SNAPSHOT test diff --git a/scm-plugin-backend/pom.xml b/scm-plugin-backend/pom.xml index 3dc928f370..1d3621b8ea 100644 --- a/scm-plugin-backend/pom.xml +++ b/scm-plugin-backend/pom.xml @@ -6,13 +6,13 @@ scm sonia.scm - 1.54 + 1.55-SNAPSHOT sonia.scm scm-plugin-backend war - 1.54 + 1.55-SNAPSHOT ${project.artifactId} @@ -62,7 +62,7 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT diff --git a/scm-plugins/pom.xml b/scm-plugins/pom.xml index 4a755d83b6..4ef5f4bfbf 100644 --- a/scm-plugins/pom.xml +++ b/scm-plugins/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54 + 1.55-SNAPSHOT sonia.scm.plugins scm-plugins pom - 1.54 + 1.55-SNAPSHOT scm-plugins @@ -26,7 +26,7 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT @@ -59,7 +59,7 @@ sonia.scm.maven scm-maven-plugin - 1.54 + 1.55-SNAPSHOT process-resources diff --git a/scm-plugins/scm-git-plugin/pom.xml b/scm-plugins/scm-git-plugin/pom.xml index 205ee00760..695bfba8fd 100644 --- a/scm-plugins/scm-git-plugin/pom.xml +++ b/scm-plugins/scm-git-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.54 + 1.55-SNAPSHOT sonia.scm.plugins scm-git-plugin - 1.54 + 1.55-SNAPSHOT scm-git-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Git @@ -54,7 +54,7 @@ sonia.scm scm-test - 1.54 + 1.55-SNAPSHOT test diff --git a/scm-plugins/scm-hg-plugin/pom.xml b/scm-plugins/scm-hg-plugin/pom.xml index 4133064da1..a9281ffb37 100644 --- a/scm-plugins/scm-hg-plugin/pom.xml +++ b/scm-plugins/scm-hg-plugin/pom.xml @@ -6,12 +6,12 @@ sonia.scm.plugins scm-plugins - 1.54 + 1.55-SNAPSHOT sonia.scm.plugins scm-hg-plugin - 1.54 + 1.55-SNAPSHOT scm-hg-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Mercurial @@ -42,7 +42,7 @@ sonia.scm scm-test - 1.54 + 1.55-SNAPSHOT test diff --git a/scm-plugins/scm-svn-plugin/pom.xml b/scm-plugins/scm-svn-plugin/pom.xml index 3cc04eecbc..555f50f3ed 100644 --- a/scm-plugins/scm-svn-plugin/pom.xml +++ b/scm-plugins/scm-svn-plugin/pom.xml @@ -6,12 +6,12 @@ scm-plugins sonia.scm.plugins - 1.54 + 1.55-SNAPSHOT sonia.scm.plugins scm-svn-plugin - 1.54 + 1.55-SNAPSHOT scm-svn-plugin https://bitbucket.org/sdorra/scm-manager Plugin for the version control system Subversion @@ -48,7 +48,7 @@ sonia.scm scm-test - 1.54 + 1.55-SNAPSHOT test diff --git a/scm-samples/pom.xml b/scm-samples/pom.xml index 8d1ad94701..fd83551108 100644 --- a/scm-samples/pom.xml +++ b/scm-samples/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54 + 1.55-SNAPSHOT sonia.scm.samples scm-samples pom - 1.54 + 1.55-SNAPSHOT scm-samples diff --git a/scm-samples/scm-sample-auth/pom.xml b/scm-samples/scm-sample-auth/pom.xml index 6992651de1..71e7568e95 100644 --- a/scm-samples/scm-sample-auth/pom.xml +++ b/scm-samples/scm-sample-auth/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.54 + 1.55-SNAPSHOT sonia.scm.sample scm-sample-auth - 1.54 + 1.55-SNAPSHOT scm-sample-auth Sample Authentication Plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT diff --git a/scm-samples/scm-sample-hello/pom.xml b/scm-samples/scm-sample-hello/pom.xml index 8e142b86b8..8afc4c768e 100644 --- a/scm-samples/scm-sample-hello/pom.xml +++ b/scm-samples/scm-sample-hello/pom.xml @@ -6,12 +6,12 @@ scm-samples sonia.scm.samples - 1.54 + 1.55-SNAPSHOT sonia.scm.sample scm-sample-hello - 1.54 + 1.55-SNAPSHOT scm-sample-hello A simple hello world plugin https://bitbucket.org/sdorra/scm-manager @@ -28,7 +28,7 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT diff --git a/scm-server/pom.xml b/scm-server/pom.xml index e6756b9edd..7f2d908206 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.54 + 1.55-SNAPSHOT sonia.scm scm-server - 1.54 + 1.55-SNAPSHOT scm-server jar diff --git a/scm-test/pom.xml b/scm-test/pom.xml index 6b4f34760f..062e0f68f1 100644 --- a/scm-test/pom.xml +++ b/scm-test/pom.xml @@ -6,12 +6,12 @@ scm sonia.scm - 1.54 + 1.55-SNAPSHOT sonia.scm scm-test - 1.54 + 1.55-SNAPSHOT scm-test @@ -25,7 +25,7 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 0e62ad77cf..0d5629671c 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54 + 1.55-SNAPSHOT sonia.scm scm-webapp war - 1.54 + 1.55-SNAPSHOT scm-webapp @@ -38,31 +38,31 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT sonia.scm scm-dao-xml - 1.54 + 1.55-SNAPSHOT sonia.scm.plugins scm-hg-plugin - 1.54 + 1.55-SNAPSHOT sonia.scm.plugins scm-svn-plugin - 1.54 + 1.55-SNAPSHOT sonia.scm.plugins scm-git-plugin - 1.54 + 1.55-SNAPSHOT @@ -285,7 +285,7 @@ sonia.scm scm-test - 1.54 + 1.55-SNAPSHOT test @@ -298,7 +298,7 @@ sonia.scm.plugins scm-git-plugin - 1.54 + 1.55-SNAPSHOT tests test @@ -306,7 +306,7 @@ sonia.scm.plugins scm-hg-plugin - 1.54 + 1.55-SNAPSHOT tests test @@ -314,7 +314,7 @@ sonia.scm.plugins scm-svn-plugin - 1.54 + 1.55-SNAPSHOT tests test @@ -558,7 +558,7 @@ sonia.scm scm-dao-orientdb - 1.54 + 1.55-SNAPSHOT diff --git a/support/pom.xml b/support/pom.xml index 43559ba521..3f770d8f3c 100644 --- a/support/pom.xml +++ b/support/pom.xml @@ -6,13 +6,13 @@ sonia.scm scm - 1.54 + 1.55-SNAPSHOT sonia.scm.support scm-support pom - 1.54 + 1.55-SNAPSHOT scm-support diff --git a/support/scm-support-btrace/pom.xml b/support/scm-support-btrace/pom.xml index 98e16acdc6..98eb9981c4 100644 --- a/support/scm-support-btrace/pom.xml +++ b/support/scm-support-btrace/pom.xml @@ -4,12 +4,12 @@ sonia.scm.support scm-support - 1.54 + 1.55-SNAPSHOT sonia.scm scm-support-btrace - 1.54 + 1.55-SNAPSHOT jar scm-support-btrace @@ -18,7 +18,7 @@ sonia.scm scm-core - 1.54 + 1.55-SNAPSHOT From 73cfbe8993c06ee735cc31f7289a3e417fe3337f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 25 Jun 2017 13:46:39 +0200 Subject: [PATCH 56/56] remove work directory after package upgrade, see #923 --- scm-server/pom.xml | 1 + scm-server/src/main/nativepkg/clear-cache | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 scm-server/src/main/nativepkg/clear-cache diff --git a/scm-server/pom.xml b/scm-server/pom.xml index 7f2d908206..e2005c6fbe 100644 --- a/scm-server/pom.xml +++ b/scm-server/pom.xml @@ -205,6 +205,7 @@ ${project.basedir}/src/main/nativepkg/create-user + ${project.basedir}/src/main/nativepkg/clear-cache diff --git a/scm-server/src/main/nativepkg/clear-cache b/scm-server/src/main/nativepkg/clear-cache new file mode 100644 index 0000000000..01d7140357 --- /dev/null +++ b/scm-server/src/main/nativepkg/clear-cache @@ -0,0 +1,9 @@ +#!/bin/sh + +# clear workdir after upgrade +# https://bitbucket.org/sdorra/scm-manager/issues/923/scmmanager-installed-from-debian-package + +WORKDIR="/var/cache/scm/work/webapp" +if [ -d "${WORKDIR}" ]; then + rm -rf "${WORKDIR}" +fi