From 9b850543cd2b9c55e1006528bed94fd4037a4d1d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 6 Jan 2014 11:37:59 +0100 Subject: [PATCH 01/22] added readme for the 1.x branch --- README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..281211cbab --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# SCM-Manager + +This branch (1.x) is for the development of scm-manager 1.x. If you are interested in the development of version 2.x, please checkout the default branch. \ No newline at end of file From 2e3bd0ef8f2950243e98a356d75d00767dfa40d7 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 7 Jan 2014 08:08:32 +0100 Subject: [PATCH 02/22] fix double slash for append and getCompleteUrl of HttpUtil --- .../main/java/sonia/scm/util/HttpUtil.java | 19 ++++-------- .../java/sonia/scm/util/HttpUtilTest.java | 31 +++++++++++++++++++ 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java index 96eab99483..f1ce453cc1 100644 --- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java @@ -197,7 +197,11 @@ public final class HttpUtil */ public static String append(String uri, String suffix) { - if (!uri.endsWith(SEPARATOR_PATH) &&!suffix.startsWith(SEPARATOR_PATH)) + if ( uri.endsWith(SEPARATOR_PATH) && suffix.startsWith(SEPARATOR_PATH) ) + { + uri = uri.substring( 0, uri.length() - 1 ); + } + else if (!uri.endsWith(SEPARATOR_PATH) &&!suffix.startsWith(SEPARATOR_PATH)) { uri = uri.concat(SEPARATOR_PATH); } @@ -490,18 +494,7 @@ public final class HttpUtil public static String getCompleteUrl(ScmConfiguration configuration, String path) { - String url = configuration.getBaseUrl(); - - if (url.endsWith(SEPARATOR_PATH) && path.startsWith(SEPARATOR_PATH)) - { - url = url.substring(0, url.length()); - } - else if (!path.startsWith(SEPARATOR_PATH)) - { - path = SEPARATOR_PATH.concat(path); - } - - return url.concat(path); + return append(configuration.getBaseUrl(), path); } /** diff --git a/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java b/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java index d2483c219f..4f88616662 100644 --- a/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/util/HttpUtilTest.java @@ -54,6 +54,33 @@ import javax.servlet.http.HttpServletRequest; public class HttpUtilTest { + /** + * Method description + * + */ + @Test + public void appendTest() + { + //J- + assertEquals( + "http://www.scm-manager/scm/test", + HttpUtil.append("http://www.scm-manager/scm/", "test") + ); + assertEquals( + "http://www.scm-manager/scm/test", + HttpUtil.append("http://www.scm-manager/scm", "test") + ); + assertEquals( + "http://www.scm-manager/scm/test", + HttpUtil.append("http://www.scm-manager/scm", "/test") + ); + assertEquals( + "http://www.scm-manager/scm/test", + HttpUtil.append("http://www.scm-manager/scm/", "/test") + ); + //J+ + } + /** * Method description * @@ -173,6 +200,10 @@ public class HttpUtilTest HttpUtil.getCompleteUrl(config, "test/path")); assertEquals("http://www.scm-manager.org/scm/test/path", HttpUtil.getCompleteUrl(config, "/test/path")); + + config.setBaseUrl("http://www.scm-manager.org/scm/"); + assertEquals("http://www.scm-manager.org/scm/test/path", + HttpUtil.getCompleteUrl(config, "/test/path")); } /** From 20ecc2ee7b36c94d55ca42e04aa0fafdb50d95cc Mon Sep 17 00:00:00 2001 From: pankaj azad Date: Wed, 8 Jan 2014 14:30:00 +0000 Subject: [PATCH 03/22] shBrushCpp.js edited online with Bitbucket --- .../webapp/resources/syntaxhighlighter/scripts/shBrushCpp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/main/webapp/resources/syntaxhighlighter/scripts/shBrushCpp.js b/scm-webapp/src/main/webapp/resources/syntaxhighlighter/scripts/shBrushCpp.js index 9f70d3aed6..198f2ed457 100644 --- a/scm-webapp/src/main/webapp/resources/syntaxhighlighter/scripts/shBrushCpp.js +++ b/scm-webapp/src/main/webapp/resources/syntaxhighlighter/scripts/shBrushCpp.js @@ -88,7 +88,7 @@ }; Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['cpp', 'c']; + Brush.aliases = ['cpp', 'hpp', 'hxx', 'cxx', 'hh', 'cc', 'c', 'h']; SyntaxHighlighter.brushes.Cpp = Brush; From aa9f8f6592ea929d367ab9114d263dda892149cf Mon Sep 17 00:00:00 2001 From: pankaj azad Date: Wed, 8 Jan 2014 14:33:32 +0000 Subject: [PATCH 04/22] shBrushJava.js edited online with Bitbucket --- .../webapp/resources/syntaxhighlighter/scripts/shBrushJava.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-webapp/src/main/webapp/resources/syntaxhighlighter/scripts/shBrushJava.js b/scm-webapp/src/main/webapp/resources/syntaxhighlighter/scripts/shBrushJava.js index 62fa114a40..372557a1df 100644 --- a/scm-webapp/src/main/webapp/resources/syntaxhighlighter/scripts/shBrushJava.js +++ b/scm-webapp/src/main/webapp/resources/syntaxhighlighter/scripts/shBrushJava.js @@ -32,7 +32,7 @@ }; Brush.prototype = new SyntaxHighlighter.Highlighter(); - Brush.aliases = ['java']; + Brush.aliases = ['java','idl']; SyntaxHighlighter.brushes.Java = Brush; From c8db162bfaa048a59f5dd3d0deb9ad4f4db005d1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 9 Jan 2014 19:27:09 +0100 Subject: [PATCH 05/22] use final field and fixed a precondition check --- .../src/main/java/sonia/scm/store/FileBasedStore.java | 2 +- .../main/java/sonia/scm/store/FileBasedStoreFactory.java | 4 ++-- scm-dao-xml/src/main/java/sonia/scm/store/FileBlob.java | 6 +++--- .../src/main/java/sonia/scm/store/FileBlobStore.java | 6 +++--- .../src/main/java/sonia/scm/store/FileBlobStoreFactory.java | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStore.java index 0d1d250ce7..07a841a777 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStore.java @@ -199,5 +199,5 @@ public abstract class FileBasedStore implements StoreBase protected File directory; /** Field description */ - private String suffix; + private final String suffix; } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java index 989878a722..5fff61de3a 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/FileBasedStoreFactory.java @@ -105,11 +105,11 @@ public class FileBasedStoreFactory //~--- fields --------------------------------------------------------------- /** Field description */ - private SCMContextProvider context; + private final SCMContextProvider context; /** Field description */ private File dataDirectory; /** Field description */ - private String dataDirectoryName; + private final String dataDirectoryName; } 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 8452956ea9..9c4cba88d9 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 @@ -46,7 +46,7 @@ import java.io.OutputStream; * * @author Sebastian Sdorra */ -public class FileBlob implements Blob +public final class FileBlob implements Blob { /** @@ -122,8 +122,8 @@ public class FileBlob implements Blob //~--- fields --------------------------------------------------------------- /** Field description */ - private File file; + private final File file; /** Field description */ - private String id; + private final String id; } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/FileBlobStore.java b/scm-dao-xml/src/main/java/sonia/scm/store/FileBlobStore.java index aae0cdf8b3..690c42fb33 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/FileBlobStore.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/FileBlobStore.java @@ -142,7 +142,7 @@ public class FileBlobStore extends FileBasedStore implements BlobStore @Override public void remove(Blob blob) { - Preconditions.checkNotNull("blob argument is required"); + Preconditions.checkNotNull(blob, "blob argument is required"); remove(blob.getId()); } @@ -196,6 +196,6 @@ public class FileBlobStore extends FileBasedStore implements BlobStore //~--- fields --------------------------------------------------------------- - /** Field description */ - private KeyGenerator keyGenerator; + /** key generator */ + private final KeyGenerator keyGenerator; } diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/FileBlobStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/FileBlobStoreFactory.java index 82fc587d0b..93d525026c 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/FileBlobStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/FileBlobStoreFactory.java @@ -98,6 +98,6 @@ public class FileBlobStoreFactory extends FileBasedStoreFactory //~--- fields --------------------------------------------------------------- - /** Field description */ - private KeyGenerator keyGenerator; + /** key generator */ + private final KeyGenerator keyGenerator; } From bc61218bf8ca062eb4a9d82d2fc83fc5a93a75c3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 17 Jan 2014 15:56:18 +0100 Subject: [PATCH 06/22] fix bug with passwords which contains a colon --- .../sonia/scm/web/HgHookCallbackServlet.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java index 38240eb876..ff4c9863c0 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgHookCallbackServlet.java @@ -242,14 +242,25 @@ public class HgHookCallbackServlet extends HttpServlet if (Util.isNotEmpty(credentials)) { - String[] credentialsArray = credentials.split(":"); + int index = credentials.indexOf(':'); - if (credentialsArray.length >= 2) + if (index > 0 && index < credentials.length()) { Subject subject = SecurityUtils.getSubject(); - subject.login(Tokens.createAuthenticationToken(request, - credentialsArray[0], credentialsArray[1])); + //J- + subject.login( + Tokens.createAuthenticationToken( + request, + credentials.substring(0, index), + credentials.substring(index + 1) + ) + ); + //J+ + } + else + { + logger.error("could not find delimiter"); } } } From 125a04ebd6ee5467c27e58660f4279fd2e30b967 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 17 Jan 2014 18:44:16 +0100 Subject: [PATCH 07/22] close branch issue-516 From 5951b32f8ed667be65a6b5464bf608c4d22c40c1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 25 Jan 2014 11:50:18 +0100 Subject: [PATCH 08/22] added main method for an easy way to generate static keys --- .../sonia/scm/security/DefaultKeyGenerator.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultKeyGenerator.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultKeyGenerator.java index 2ed55e4487..149840b106 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultKeyGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultKeyGenerator.java @@ -30,6 +30,7 @@ */ + package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- @@ -68,6 +69,17 @@ public class DefaultKeyGenerator implements KeyGenerator //~--- methods -------------------------------------------------------------- + /** + * Method description + * + * + * @param args + */ + public static void main(String[] args) + { + System.out.println(new DefaultKeyGenerator().createKey()); + } + /** * Method description * @@ -107,8 +119,8 @@ public class DefaultKeyGenerator implements KeyGenerator //~--- fields --------------------------------------------------------------- /** Field description */ - private AtomicLong sessionKey = new AtomicLong(); + private final AtomicLong sessionKey = new AtomicLong(); /** Field description */ - private Random random = new Random(); + private final Random random = new Random(); } From e6cbb6a4cf268e0b7a93e1a4d28dac11c0bad9a2 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 25 Jan 2014 12:07:38 +0100 Subject: [PATCH 09/22] start implementation of repository health checks --- .../AbstractSimpleRepositoryHandler.java | 3 +- .../scm/repository/DirectoryHealthCheck.java | 212 ++++++++++++++++ .../sonia/scm/repository/HealthCheck.java | 54 +++++ .../scm/repository/HealthCheckFailure.java | 212 ++++++++++++++++ .../scm/repository/HealthCheckResult.java | 186 ++++++++++++++ .../java/sonia/scm/repository/Repository.java | 64 ++++- .../RepositoryDirectoryHandler.java | 55 +++++ .../scm/repository/DBFormatHealthCheck.java | 228 ++++++++++++++++++ .../java/sonia/scm/ScmContextListener.java | 3 + .../main/java/sonia/scm/ScmServletModule.java | 5 + .../HealthCheckContextListener.java | 153 ++++++++++++ .../sonia/scm/repository/HealthChecker.java | 158 ++++++++++++ 12 files changed, 1329 insertions(+), 4 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/repository/DirectoryHealthCheck.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/HealthCheck.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/RepositoryDirectoryHandler.java create mode 100644 scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/DBFormatHealthCheck.java create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/HealthCheckContextListener.java create mode 100644 scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java diff --git a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java index 20710a20ad..b76510a36a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java +++ b/scm-core/src/main/java/sonia/scm/repository/AbstractSimpleRepositoryHandler.java @@ -64,7 +64,7 @@ import java.net.URL; * @param */ public abstract class AbstractSimpleRepositoryHandler - extends AbstractRepositoryHandler + extends AbstractRepositoryHandler implements RepositoryDirectoryHandler { /** Field description */ @@ -249,6 +249,7 @@ public abstract class AbstractSimpleRepositoryHandlerof()); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param failures + */ + private HealthCheckResult(Set failures) + { + this.failures = failures; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public static HealthCheckResult healthy() + { + return HEALTHY; + } + + /** + * Method description + * + * + * @param failures + * + * @return + */ + public static HealthCheckResult unhealthy( + Iterable failures) + { + return new HealthCheckResult(ImmutableSet.copyOf(failures)); + } + + /** + * Method description + * + * + * @param failure + * @param otherFailures + * + * @return + */ + public static HealthCheckResult unhealthy(HealthCheckFailure failure, + HealthCheckFailure... otherFailures) + { + //J- + return new HealthCheckResult( + ImmutableSet.builder() + .add(failure) + .add(otherFailures) + .build() + ); + //J+ + } + + /** + * Method description + * + * + * @param otherResult + * + * @return + */ + public HealthCheckResult merge(HealthCheckResult otherResult) + { + HealthCheckResult merged; + + if ((otherResult == null) || otherResult.isHealthy()) + { + merged = this; + } + else + { + //J- + merged = new HealthCheckResult( + ImmutableSet.builder() + .addAll(failures) + .addAll(otherResult.failures) + .build() + ); + //J+ + } + + return merged; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public Set getFailures() + { + return failures; + } + + /** + * Method description + * + * + * @return + */ + public boolean isHealthy() + { + return failures.isEmpty(); + } + + /** + * Method description + * + * + * @return + */ + public boolean isUnhealthy() + { + return !failures.isEmpty(); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final Set failures; +} diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 1193165a23..15bee4093b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -47,11 +47,13 @@ import sonia.scm.util.ValidationUtil; //~--- JDK imports ------------------------------------------------------------ import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; /** @@ -59,8 +61,8 @@ import javax.xml.bind.annotation.XmlRootElement; * * @author Sebastian Sdorra */ -@XmlRootElement(name = "repositories") @XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "repositories") public class Repository extends BasicPropertiesAware implements ModelObject { @@ -163,6 +165,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject repository.setUrl(url); repository.setPublicReadable(publicReadable); repository.setArchived(archived); + + // do not copy health check results } /** @@ -216,7 +220,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject && Objects.equal(url, other.url) && Objects.equal(creationDate, other.creationDate) && Objects.equal(lastModified, other.lastModified) - && Objects.equal(properties, other.properties); + && Objects.equal(properties, other.properties) + && Objects.equal(healthCheckFailures, other.healthCheckFailures); //J+ } @@ -230,7 +235,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject public int hashCode() { return Objects.hashCode(id, name, contact, description, publicReadable, - archived, permissions, type, url, creationDate, lastModified, properties); + archived, permissions, type, url, creationDate, lastModified, properties, + healthCheckFailures); } /** @@ -256,6 +262,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject .add("lastModified", lastModified) .add("creationDate", creationDate) .add("properties", properties) + .add("healthCheckFailures", healthCheckFailures) .toString(); //J+ } @@ -296,6 +303,24 @@ public class Repository extends BasicPropertiesAware implements ModelObject return description; } + /** + * Method description + * + * + * @return + * @since 1.36 + */ + @SuppressWarnings("unchecked") + public List getHealthCheckFailures() + { + if (healthCheckFailures == null) + { + healthCheckFailures = Collections.EMPTY_LIST; + } + + return healthCheckFailures; + } + /** * Returns the unique id of the {@link Repository}. * @@ -384,6 +409,19 @@ public class Repository extends BasicPropertiesAware implements ModelObject return archived; } + /** + * Method description + * + * + * @return + * + * @since 1.36 + */ + public boolean isHealthy() + { + return Util.isEmpty(healthCheckFailures); + } + /** * Returns true if the {@link Repository} is public readable. * @@ -542,6 +580,19 @@ public class Repository extends BasicPropertiesAware implements ModelObject this.url = url; } + /** + * Method description + * + * + * @param healthCheckFailures + * + * @since 1.36 + */ + void setHealthCheckFailures(List healthCheckFailures) + { + this.healthCheckFailures = healthCheckFailures; + } + //~--- fields --------------------------------------------------------------- /** Field description */ @@ -553,6 +604,13 @@ public class Repository extends BasicPropertiesAware implements ModelObject /** Field description */ private String description; + /** + * @since 1.36 + */ + @XmlElement(name = "healthCheckFailure") + @XmlElementWrapper(name = "healthCheckFailures") + private List healthCheckFailures; + /** Field description */ private String id; diff --git a/scm-core/src/main/java/sonia/scm/repository/RepositoryDirectoryHandler.java b/scm-core/src/main/java/sonia/scm/repository/RepositoryDirectoryHandler.java new file mode 100644 index 0000000000..3fda4a76be --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/RepositoryDirectoryHandler.java @@ -0,0 +1,55 @@ +/** + * 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; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; + +/** + * + * @author Sebastian Sdorra + * @since 1.36 + */ +public interface RepositoryDirectoryHandler extends RepositoryHandler +{ + + /** + * Method description + * + * + * @param repository + * + * @return + */ + public File getDirectory(Repository repository); +} diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/DBFormatHealthCheck.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/DBFormatHealthCheck.java new file mode 100644 index 0000000000..c9958f24fb --- /dev/null +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/DBFormatHealthCheck.java @@ -0,0 +1,228 @@ +/** + * 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; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import com.google.inject.Inject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.tmatesoft.svn.core.SVNException; +import org.tmatesoft.svn.core.SVNURL; +import org.tmatesoft.svn.core.io.SVNRepository; +import org.tmatesoft.svn.core.io.SVNRepositoryFactory; + +import sonia.scm.plugin.ext.Extension; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.File; +import java.io.IOException; + +import java.util.List; +import java.util.Set; + +/** + * + * @author Sebastian Sdorra + */ +@Extension +public class DBFormatHealthCheck extends DirectoryHealthCheck +{ + + /** + * the logger for DBFormatHealthCheck + */ + private static final Logger logger = + LoggerFactory.getLogger(DBFormatHealthCheck.class); + + /** Field description */ + private static final Set INVALID_DBFORMAT = ImmutableSet.of("5"); + + /** Field description */ + private static final HealthCheckFailure INCOMPATIBLE_DB_FORMAT = + new HealthCheckFailure("AnOTx99ex1", "Incompatible DB Format", + "https://bitbucket.org/sdorra/scm-manager/wiki/healtchecks/svn-incompatible-dbformat", + "The subversion db format is incompatible with the svn version used within scm-manager."); + + /** Field description */ + private static final String DBFORMAT = + "db".concat(File.separator).concat("format"); + + /** Field description */ + private static final HealthCheckFailure COULD_NOT_READ_DB_FILE = + new HealthCheckFailure("4IOTx8pvv1", "Could not read db/format file", + "The db/format file of the repository was not readable."); + + /** Field description */ + private static final HealthCheckFailure COULD_NOT_OPEN_REPOSITORY = + new HealthCheckFailure("6TOTx9RLD1", "Could not open svn repository", + "The repository is not openable."); + + /** Field description */ + private static final HealthCheckFailure COULD_NOT_FIND_DB_FILE = + new HealthCheckFailure("A9OTx8leC1", "Could not find db/format file", + "The subversion repository does not contain the db/format file."); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param repositoryManager + */ + @Inject + public DBFormatHealthCheck(RepositoryManager repositoryManager) + { + super(repositoryManager); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param repository + * @param directory + * + * @return + */ + @Override + protected HealthCheckResult check(Repository repository, File directory) + { + List failures = Lists.newArrayList(); + + checkIfRepositoryIsOpenable(failures, repository, directory); + checkForBadDBVersion(failures, repository, directory); + + return failures.isEmpty() + ? HealthCheckResult.healthy() + : HealthCheckResult.unhealthy(failures); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param repository + * + * @return + */ + @Override + protected boolean isCheckResponsible(Repository repository) + { + return SvnRepositoryHandler.TYPE_NAME.equalsIgnoreCase( + repository.getType()); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param failures + * @param repository + * @param directory + */ + private void checkForBadDBVersion(List failures, + Repository repository, File directory) + { + File dbfile = new File(directory, DBFORMAT); + + if (dbfile.exists()) + { + try + { + String content = Files.readFirstLine(dbfile, Charsets.US_ASCII); + + if ((content != null) && INVALID_DBFORMAT.contains(content.trim())) + { + failures.add(INCOMPATIBLE_DB_FORMAT); + } + } + catch (IOException ex) + { + failures.add(COULD_NOT_READ_DB_FILE); + logger.warn( + "could not read db/format of ".concat(repository.getName()), ex); + } + } + else + { + failures.add(COULD_NOT_FIND_DB_FILE); + logger.warn("repository {} does not have a {} file", + repository.getName(), DBFORMAT); + } + } + + /** + * Method description + * + * + * @param failures + * @param repository + * @param directory + */ + private void checkIfRepositoryIsOpenable(List failures, + Repository repository, File directory) + { + SVNRepository svn = null; + + try + { + svn = SVNRepositoryFactory.create(SVNURL.fromFile(directory)); + } + catch (SVNException ex) + { + + failures.add(COULD_NOT_OPEN_REPOSITORY); + + logger.warn( + "Could not open svn repository ".concat(repository.getName()), ex); + } + finally + { + SvnUtil.closeSession(svn); + } + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index aeda5c6ddf..3845c985f7 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -59,6 +59,7 @@ import java.util.List; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; +import sonia.scm.repository.HealthCheckContextListener; /** * @@ -141,6 +142,8 @@ public class ScmContextListener extends GuiceServletContextListener // init servlet context listeners globalInjector.getInstance(ServletContextListenerHolder.class) .contextInitialized(servletContextEvent); + globalInjector.getInstance(HealthCheckContextListener.class) + .contextInitialized(servletContextEvent); //J+ } } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 6d8b379c64..4ae22b64a3 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -154,6 +154,8 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; +import sonia.scm.repository.HealthCheckContextListener; +import sonia.scm.repository.HealthChecker; /** * @@ -275,6 +277,9 @@ public class ScmServletModule extends ServletModule bind(CipherHandler.class).toInstance(cu.getCipherHandler()); bind(EncryptionHandler.class, MessageDigestEncryptionHandler.class); bind(FileSystem.class, DefaultFileSystem.class); + + // bind health check stuff + bind(HealthCheckContextListener.class); // bind extensions pluginLoader.processExtensions(binder()); diff --git a/scm-webapp/src/main/java/sonia/scm/repository/HealthCheckContextListener.java b/scm-webapp/src/main/java/sonia/scm/repository/HealthCheckContextListener.java new file mode 100644 index 0000000000..545871b508 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/HealthCheckContextListener.java @@ -0,0 +1,153 @@ +/** + * 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; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; + +import org.apache.shiro.SecurityUtils; + +import sonia.scm.plugin.ext.Extension; +import sonia.scm.web.security.AdministrationContext; +import sonia.scm.web.security.PrivilegedAction; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * + * @author Sebastian Sdorra + */ +@Extension +public class HealthCheckContextListener implements ServletContextListener +{ + + /** + * Constructs ... + * + * + * @param context + */ + @Inject + public HealthCheckContextListener(AdministrationContext context) + { + this.context = context; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param sce + */ + @Override + public void contextDestroyed(ServletContextEvent sce) + { + + // do nothing + } + + /** + * Method description + * + * + * @param sce + */ + @Override + public void contextInitialized(ServletContextEvent sce) + { + context.runAsAdmin(HealthCheckStartupAction.class); + } + + //~--- inner classes -------------------------------------------------------- + + /** + * Class description + * + * + * @version Enter version here..., 14/01/23 + * @author Enter your name here... + */ + static class HealthCheckStartupAction implements PrivilegedAction + { + + /** + * Constructs ... + * + * + * @param healthChecker + */ + @Inject + public HealthCheckStartupAction(HealthChecker healthChecker) + { + this.healthChecker = healthChecker; + } + + //~--- methods ------------------------------------------------------------ + + /** + * Method description + * + */ + @Override + public void run() + { + + // excute health checks for all repsitories asynchronous + SecurityUtils.getSubject().execute(new Runnable() + { + + @Override + public void run() + { + healthChecker.checkAll(); + } + }); + } + + //~--- fields ------------------------------------------------------------- + + /** Field description */ + private final HealthChecker healthChecker; + } + + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final AdministrationContext context; +} diff --git a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java new file mode 100644 index 0000000000..7446fb0086 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java @@ -0,0 +1,158 @@ +/** + * 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; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.collect.ImmutableList; +import com.google.inject.Inject; + +import org.apache.shiro.SecurityUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.security.Role; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import java.util.Set; + +/** + * + * @author Sebastian Sdorra + */ +public final class HealthChecker +{ + + /** + * the logger for HealthChecker + */ + private static final Logger logger = + LoggerFactory.getLogger(HealthChecker.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param checks + * @param repositoryManager + */ + @Inject + public HealthChecker(Set checks, + RepositoryManager repositoryManager) + { + this.checks = checks; + this.repositoryManager = repositoryManager; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param repository + * + * @throws IOException + * @throws RepositoryException + */ + public void check(Repository repository) + throws RepositoryException, IOException + { + logger.info("start health check for repository {}", repository.getName()); + SecurityUtils.getSubject().checkRole(Role.ADMIN); + + HealthCheckResult result = HealthCheckResult.healthy(); + + for (HealthCheck check : checks) + { + logger.trace("execute health check {} for repository {}", + check.getClass(), repository.getName()); + result = result.merge(check.check(repository)); + } + + if (result.isUnhealthy()) + { + logger.warn("repository {} is unhealthy: {}", repository.getName(), + result); + } + else + { + logger.info("repository {} is healthy", repository.getName()); + } + + if (!(repository.isHealthy() && result.isHealthy())) + { + logger.trace("store health check results for repository {}", + repository.getName()); + repository.setHealthCheckFailures( + ImmutableList.copyOf(result.getFailures())); + repositoryManager.modify(repository); + } + } + + /** + * Method description + * + * + */ + public void checkAll() + { + logger.debug("check health of all repositories"); + SecurityUtils.getSubject().checkRole(Role.ADMIN); + + for (Repository repository : repositoryManager.getAll()) + { + try + { + check(repository); + } + catch (Exception ex) + { + logger.error("health check ends with exception", ex); + } + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private final Set checks; + + /** Field description */ + private final RepositoryManager repositoryManager; +} From 9e9351fe86689684387df6767f1898a57d824df6 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 25 Jan 2014 13:26:43 +0100 Subject: [PATCH 10/22] display failed health checks in user interface --- .../java/sonia/scm/repository/Repository.java | 2 +- .../rest/resources/RepositoryResource.java | 7 +- scm-webapp/src/main/webapp/index.mustache | 1 + .../src/main/webapp/resources/css/style.css | 4 + .../main/webapp/resources/images/warning.png | Bin 0 -> 666 bytes .../js/repository/sonia.repository.grid.js | 37 +++++- .../sonia.repository.healthcheckfailure.js | 108 ++++++++++++++++++ 7 files changed, 152 insertions(+), 7 deletions(-) create mode 100755 scm-webapp/src/main/webapp/resources/images/warning.png create mode 100644 scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 15bee4093b..6d438f4d9a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -588,7 +588,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject * * @since 1.36 */ - void setHealthCheckFailures(List healthCheckFailures) + public void setHealthCheckFailures(List healthCheckFailures) { this.healthCheckFailures = healthCheckFailures; } 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 bd391eeeb1..f3e828c5db 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 @@ -1077,6 +1077,7 @@ public class RepositoryResource repository.setProperties(null); repository.setPermissions(null); + repository.setHealthCheckFailures(null); } } @@ -1135,11 +1136,11 @@ public class RepositoryResource //~--- fields --------------------------------------------------------------- /** Field description */ - private ScmConfiguration configuration; + private final ScmConfiguration configuration; /** Field description */ - private RepositoryManager repositoryManager; + private final RepositoryManager repositoryManager; /** Field description */ - private RepositoryServiceFactory servicefactory; + private final RepositoryServiceFactory servicefactory; } diff --git a/scm-webapp/src/main/webapp/index.mustache b/scm-webapp/src/main/webapp/index.mustache index b67ec3f457..e29c2cd556 100644 --- a/scm-webapp/src/main/webapp/index.mustache +++ b/scm-webapp/src/main/webapp/index.mustache @@ -131,6 +131,7 @@ + diff --git a/scm-webapp/src/main/webapp/resources/css/style.css b/scm-webapp/src/main/webapp/resources/css/style.css index dda252141a..2ce002295c 100644 --- a/scm-webapp/src/main/webapp/resources/css/style.css +++ b/scm-webapp/src/main/webapp/resources/css/style.css @@ -234,3 +234,7 @@ div.noscript-container h1 { .upload-icon { background: url('../images/add.png') no-repeat 0 0 !important; } + +.unhealthy { + color: red; +} diff --git a/scm-webapp/src/main/webapp/resources/images/warning.png b/scm-webapp/src/main/webapp/resources/images/warning.png new file mode 100755 index 0000000000000000000000000000000000000000..628cf2dae3d419ae220c8928ac71393b480745a3 GIT binary patch literal 666 zcmV;L0%iS)P)eOSYYtbpBV}~vsBnU!_?2tr-P=|^T zED%wc9ezHgW@NMb!^uT_|SvCpFLJylbx zY%bpaTGI8IYXMN$9w<3j9VkA~NYOKEQXsj?6a9_hcwfU$acAhJhB)zb_w@MVUEy@S zX&I>K-R!bhu3?(6bHWIg$HEl7{9g>>&l_qdd+UYb(1~BCo9LptNq&8>!yoJ3Ui(i5 zRJ|XnYBklL!{@$-7=3mJ>P@1c=7Oc79e-V7yf+%lD2!I;Y&nXBZ>=B!5?CB>LvEx6 znI%n)qqi$#X#wKB(U7XP2P=+4{b@j#r%9-K(8UqtSDk>0UKzf*HM9yqMZ1D!$2MdZ zR=`U>0zhOH1XqN?nY@AQqB7)Fp4{v&dKXvb43hZKvnN8;Po;+jY*}~*Z|W9Q0W%{D z^T}Cc<|r(Su=1K=P5>Z4 zg`et&Va}tdzBS-G-ZcO)zCWpJvGQwrHZ`@wpM420ac@bI5~KkTFfGEM3sPWO8co4^fI6lPnA)Y{ef%@{+SnoUk0+dW+*{8WvF8}}l07*qoM6N<$g7cXs A&j0`b literal 0 HcmV?d00001 diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js index c086072f66..7e5dd6ebed 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js @@ -45,6 +45,7 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, { unknownType: 'Unknown', archiveIcon: 'resources/images/archive.png', + warningIcon: 'resources/images/warning.png', filterRequest: null, @@ -97,6 +98,8 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, { name: 'properties' },{ name: 'archived' + },{ + name: 'healthCheckFailures' }] }), sortInfo: { @@ -215,7 +218,15 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, { forceFit: true, groupMode: 'value', enableGroupingMenu: false, - groupTextTpl: '{group} ({[values.rs.length]} {[values.rs.length > 1 ? "Repositories" : "Repository"]})' + groupTextTpl: '{group} ({[values.rs.length]} {[values.rs.length > 1 ? "Repositories" : "Repository"]})', + getRowClass: function(record){ + var rowClass = ''; + var healthFailures = record.get('healthCheckFailures'); + if (healthFailures && healthFailures.length > 0){ + rowClass = 'unhealthy'; + } + return rowClass; + } }) }; @@ -229,8 +240,21 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, { } }, - renderTypeIcon: function(type){ - var result = ''; + renderTypeIcon: function(type, meta, record){ + var result; + if ( record ){ + var healthFailures = record.get('healthCheckFailures'); + if (healthFailures && healthFailures.length > 0){ + result = String.format(this.typeIconTemplate, this.warningIcon, type, type); + } + } + if (!result){ + result = this.getTypeIcon(type); + } + return result; + }, + + getTypeIcon: function(type){ var icon = Sonia.repository.getTypeIcon(type); if ( icon ){ var displayName = type; @@ -429,6 +453,13 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, { Ext.getCmp('repoRmButton').setDisabled(true); } + if (admin && item.healthCheckFailures && item.healthCheckFailures.length > 0){ + panels.push({ + xtype: 'repositoryHealthCheckFailurePanel', + healthCheckFailures: item.healthCheckFailures + }); + } + // call open listeners Ext.each(Sonia.repository.openListeners, function(listener){ if (Ext.isFunction(listener)){ diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js new file mode 100644 index 0000000000..bdc0a46ded --- /dev/null +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js @@ -0,0 +1,108 @@ +/* * + * 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 + * + */ + + +Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, { + + title: 'Health check', + linkTemplate: '{0}', + + initComponent: function(){ + var items = []; + + if ( this.healthCheckFailures && this.healthCheckFailures.length > 0 ){ + for (var i=0; i0){ + this.appendSpacer(items); + } + this.appendHealthCheckFailures(items,this.healthCheckFailures[i]); + } + } + + var config = { + title: this.title, + padding: 5, + bodyCssClass: 'x-panel-mc', + layout: 'table', + layoutConfig: { + columns: 2, + tableAttrs: { + style: 'width: 80%;' + } + }, + defaults: { + style: 'font-size: 12px' + }, + items: items + }; + Ext.apply(this, Ext.apply(this.initialConfig, config)); + Sonia.repository.HealthCheckFailure.superclass.initComponent.apply(this, arguments); + }, + + appendSpacer: function(items){ + items.push({ + xtype: 'box', + height: 10, + colspan: 2 + }); + }, + + appendHealthCheckFailures: function(items, hcf){ + items.push({ + xtype: 'label', + text: 'Summary:' + },{ + xtype: 'label', + text: hcf.summary + }); + if ( hcf.url ){ + items.push({ + xtype: 'label', + text: 'Url:' + },{ + xtype: 'box', + html: String.format(this.linkTemplate, hcf.url) + }); + } + if ( hcf.description ){ + items.push({ + xtype: 'label', + text: 'Description:' + },{ + xtype: 'label', + text: hcf.description + }); + } + } + +}); + +Ext.reg('repositoryHealthCheckFailurePanel', Sonia.repository.HealthCheckFailure); \ No newline at end of file From 08dede40fed51059eebb840d7eda93c55c484506 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 25 Jan 2014 15:00:04 +0100 Subject: [PATCH 11/22] allow rerun of healthchecks --- .../sonia/scm/repository/HealthCheck.java | 10 ++- .../scm/repository/HealthCheckFailure.java | 65 ++++++-------- .../scm/repository/HealthCheckResult.java | 87 ++++++++++++++----- .../java/sonia/scm/repository/Repository.java | 13 +-- .../rest/resources/RepositoryResource.java | 54 +++++++++++- .../sonia/scm/repository/HealthChecker.java | 27 ++++++ .../js/repository/sonia.repository.grid.js | 2 + .../sonia.repository.healthcheckfailure.js | 50 +++++++++-- 8 files changed, 226 insertions(+), 82 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/HealthCheck.java b/scm-core/src/main/java/sonia/scm/repository/HealthCheck.java index ac4ea3b654..fae422f50f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/HealthCheck.java +++ b/scm-core/src/main/java/sonia/scm/repository/HealthCheck.java @@ -31,9 +31,13 @@ package sonia.scm.repository; +//~--- non-JDK imports -------------------------------------------------------- + import sonia.scm.plugin.ExtensionPoint; /** + * Repository health check. Executes a check to verify the health + * state of a repository. * * @author Sebastian Sdorra * @since 1.36 @@ -43,12 +47,12 @@ public interface HealthCheck { /** - * Method description + * Returns the result of the repository health check. * * - * @param repository + * @param repository repository to check * - * @return + * @return result of the health check */ public HealthCheckResult check(Repository repository); } diff --git a/scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java b/scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java index a3b8ff1857..8bac16de8d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java +++ b/scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java @@ -42,6 +42,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; /** + * Single failure of a {@link HealthCheck}. * * @author Sebastian Sdorra * @since 1.36 @@ -52,17 +53,18 @@ public final class HealthCheckFailure { /** - * Constructs ... + * Constructs a new {@link HealthCheckFailure}. + * This constructor is only for JAXB. * */ HealthCheckFailure() {} /** - * Constructs ... + * Constructs a new {@link HealthCheckFailure}. * - * @param id - * @param summary - * @param description + * @param id id of the failure + * @param summary summary of the failure + * @param description description of the failure */ public HealthCheckFailure(String id, String summary, String description) { @@ -72,10 +74,10 @@ public final class HealthCheckFailure /** * Constructs ... * - * @param id - * @param summary - * @param url - * @param description + * @param id id of the failure + * @param summary summary of the failure + * @param url url of the failure + * @param description description of the failure */ public HealthCheckFailure(String id, String summary, String url, String description) @@ -89,12 +91,7 @@ public final class HealthCheckFailure //~--- methods -------------------------------------------------------------- /** - * Method description - * - * - * @param obj - * - * @return + * {@inheritDoc} */ @Override public boolean equals(Object obj) @@ -120,10 +117,7 @@ public final class HealthCheckFailure } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public int hashCode() @@ -132,10 +126,7 @@ public final class HealthCheckFailure } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public String toString() @@ -153,10 +144,9 @@ public final class HealthCheckFailure //~--- get methods ---------------------------------------------------------- /** - * Method description + * Returns the description of this failure. * - * - * @return + * @return description of this failure */ public String getDescription() { @@ -164,10 +154,9 @@ public final class HealthCheckFailure } /** - * Method description + * Returns the id of this failure. * - * - * @return + * @return id of this failure */ public String getId() { @@ -175,10 +164,9 @@ public final class HealthCheckFailure } /** - * Method description + * Returns the summary of the failure. * - * - * @return + * @return summary of the failure */ public String getSummary() { @@ -186,10 +174,9 @@ public final class HealthCheckFailure } /** - * Method description + * Return the url of the failure. * - * - * @return + * @return url of the failure */ public String getUrl() { @@ -198,15 +185,15 @@ public final class HealthCheckFailure //~--- fields --------------------------------------------------------------- - /** Field description */ + /** description of failure */ private String description; - /** Field description */ + /** id of failure */ private String id; - /** Field description */ + /** summary of failure */ private String summary; - /** Field description */ + /** url of failure */ private String url; } diff --git a/scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java b/scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java index 8f070a645f..f5cb690533 100644 --- a/scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java @@ -33,6 +33,7 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; //~--- JDK imports ------------------------------------------------------------ @@ -40,6 +41,7 @@ import com.google.common.collect.ImmutableSet; import java.util.Set; /** + * Result of {@link HealthCheck}. * * @author Sebastian Sdorra * @since 1.36 @@ -47,7 +49,7 @@ import java.util.Set; public final class HealthCheckResult { - /** Field description */ + /** healthy result */ private static final HealthCheckResult HEALTHY = new HealthCheckResult(ImmutableSet.of()); @@ -67,10 +69,9 @@ public final class HealthCheckResult //~--- methods -------------------------------------------------------------- /** - * Method description + * Returns a {@link HealthCheckResult} for a healthy repository. * - * - * @return + * @return {@link HealthCheckResult} for a healthy repository */ public static HealthCheckResult healthy() { @@ -78,12 +79,12 @@ public final class HealthCheckResult } /** - * Method description + * Returns a {@link HealthCheckResult} for a unhealthy repository. * * - * @param failures + * @param failures failures of failed {@link HealthCheck}s * - * @return + * @return {@link HealthCheckResult} for a unhealthy repository */ public static HealthCheckResult unhealthy( Iterable failures) @@ -92,13 +93,13 @@ public final class HealthCheckResult } /** - * Method description + * Returns a {@link HealthCheckResult} for a unhealthy repository. * * - * @param failure - * @param otherFailures + * @param failure failure of {@link HealthCheck} + * @param otherFailures failures of failed {@link HealthCheck}s * - * @return + * @return {@link HealthCheckResult} for a unhealthy repository */ public static HealthCheckResult unhealthy(HealthCheckFailure failure, HealthCheckFailure... otherFailures) @@ -114,12 +115,43 @@ public final class HealthCheckResult } /** - * Method description + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final HealthCheckResult other = (HealthCheckResult) obj; + + return Objects.equal(failures, other.failures); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() + { + return Objects.hashCode(failures); + } + + /** + * Merge this {@link HealthCheckResult} with another + * {@link HealthCheckResult}. * * - * @param otherResult + * @param otherResult result to merge with * - * @return + * @return merged {@link HealthCheckResult} */ public HealthCheckResult merge(HealthCheckResult otherResult) { @@ -144,13 +176,22 @@ public final class HealthCheckResult return merged; } + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return Objects.toStringHelper(this).add("failures", failures).toString(); + } + //~--- get methods ---------------------------------------------------------- /** - * Method description + * Returns a {@link Set} of {@link HealthCheckFailure}s. The set is empty if + * the repository is healthy. * - * - * @return + * @return {@link Set} of {@link HealthCheckFailure}s */ public Set getFailures() { @@ -158,10 +199,9 @@ public final class HealthCheckResult } /** - * Method description + * Returns {@code true} if the result is healthy. * - * - * @return + * @return {@code true} if the result is healthy */ public boolean isHealthy() { @@ -169,10 +209,9 @@ public final class HealthCheckResult } /** - * Method description + * Returns {@code true} if the result is unhealthy * - * - * @return + * @return {@code true} if the result is unhealthy. */ public boolean isUnhealthy() { @@ -181,6 +220,6 @@ public final class HealthCheckResult //~--- fields --------------------------------------------------------------- - /** Field description */ + /** set of failures */ private final Set failures; } diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 6d438f4d9a..88601f4451 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -304,10 +304,11 @@ public class Repository extends BasicPropertiesAware implements ModelObject } /** - * Method description + * Returns a {@link List} of {@link HealthCheckFailure}s. The {@link List} + * is empty if the repository is healthy. * * - * @return + * @return {@link List} of {@link HealthCheckFailure}s * @since 1.36 */ @SuppressWarnings("unchecked") @@ -410,10 +411,10 @@ public class Repository extends BasicPropertiesAware implements ModelObject } /** - * Method description + * Returns {@code true} if the repository is healthy. * * - * @return + * @return {@code true} if the repository is healthy * * @since 1.36 */ @@ -581,10 +582,10 @@ public class Repository extends BasicPropertiesAware implements ModelObject } /** - * Method description + * Sets {@link HealthCheckFailure} for a unhealthy repository. * * - * @param healthCheckFailures + * @param healthCheckFailures list of {@link HealthCheckFailure}s * * @since 1.36 */ 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 f3e828c5db..a6a494d5ca 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 @@ -54,6 +54,7 @@ import sonia.scm.repository.Branches; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.HealthChecker; import sonia.scm.repository.Permission; import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; @@ -131,16 +132,18 @@ public class RepositoryResource * @param configuration * @param repositoryManager * @param servicefactory + * @param healthChecker */ @Inject public RepositoryResource(ScmConfiguration configuration, RepositoryManager repositoryManager, - RepositoryServiceFactory servicefactory) + RepositoryServiceFactory servicefactory, HealthChecker healthChecker) { super(repositoryManager); this.configuration = configuration; this.repositoryManager = repositoryManager; this.servicefactory = servicefactory; + this.healthChecker = healthChecker; setDisableCache(false); } @@ -213,7 +216,7 @@ public class RepositoryResource } catch (ScmSecurityException ex) { - logger.warn("delete not allowd", ex); + logger.warn("delete not allowed", ex); response = Response.status(Response.Status.FORBIDDEN).build(); } catch (Exception ex) @@ -231,6 +234,50 @@ public class RepositoryResource return response; } + /** + * 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
  • + *
+ * + * @param id id of the repository + * + * @return + */ + @POST + @Path("{id}/healthcheck") + public Response runHealthChecks(@PathParam("id") String id) + { + Response response; + + try + { + healthChecker.check(id); + response = Response.ok().build(); + } + catch (RepositoryNotFoundException ex) + { + logger.warn("could not find repository ".concat(id), ex); + response = Response.status(Status.NOT_FOUND).build(); + } + catch (RepositoryException ex) + { + logger.error("error occured during health check", ex); + response = Response.serverError().build(); + } + catch (IOException ex) + { + logger.error("error occured during health check", ex); + response = Response.serverError().build(); + } + + return response; + } + /** * Modifies the given repository.
* This method requires owner privileges.
@@ -1138,6 +1185,9 @@ public class RepositoryResource /** Field description */ private final ScmConfiguration configuration; + /** Field description */ + private final HealthChecker healthChecker; + /** Field description */ private final RepositoryManager repositoryManager; diff --git a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java index 7446fb0086..9d609ece75 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java @@ -81,6 +81,33 @@ public final class HealthChecker //~--- methods -------------------------------------------------------------- + /** + * Method description + * + * + * @param id + * + * + * @throws IOException + * @throws RepositoryException + * @throws RepositoryNotFoundException + */ + public void check(String id) + throws RepositoryNotFoundException, RepositoryException, IOException + { + SecurityUtils.getSubject().checkRole(Role.ADMIN); + + Repository repository = repositoryManager.get(id); + + if (repository == null) + { + throw new RepositoryNotFoundException( + "could not find repository with id ".concat(id)); + } + + check(repository); + } + /** * Method description * diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js index 7e5dd6ebed..b6db7fb59f 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js @@ -456,6 +456,8 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, { if (admin && item.healthCheckFailures && item.healthCheckFailures.length > 0){ panels.push({ xtype: 'repositoryHealthCheckFailurePanel', + grid: this, + repository: item, healthCheckFailures: item.healthCheckFailures }); } diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js index bdc0a46ded..195d7d7509 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js @@ -35,17 +35,24 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, { title: 'Health check', linkTemplate: '{0}', + errorTitleText: 'Error', + errorDescriptionText: 'Could not execute health check.', + initComponent: function(){ var items = []; if ( this.healthCheckFailures && this.healthCheckFailures.length > 0 ){ for (var i=0; i0){ - this.appendSpacer(items); - } this.appendHealthCheckFailures(items,this.healthCheckFailures[i]); } } + items.push({ + xtype: 'link', + style: 'font-weight: bold', + text: 'rerun health checks', + handler: this.rerunHealthChecks, + scope: this + }); var config = { title: this.title, @@ -67,12 +74,34 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, { Sonia.repository.HealthCheckFailure.superclass.initComponent.apply(this, arguments); }, - appendSpacer: function(items){ - items.push({ - xtype: 'box', - height: 10, - colspan: 2 + rerunHealthChecks: function(){ + var url = restUrl + 'repositories/' + this.repository.id + '/healthcheck.json'; + var el = this.el; + var tid = setTimeout( function(){el.mask('Loading ...');}, 100); + + Ext.Ajax.request({ + url: url, + method: 'POST', + scope: this, + success: function(){ + clearTimeout(tid); + this.grid.reload(function(){ + this.grid.selectById(this.repository.id); + }, this); + el.unmask(); + }, + failure: function(result){ + clearTimeout(tid); + el.unmask(); + main.handleFailure( + result.status, + this.errorTitleText, + this.errorDescriptionText + ); + } }); + + this.grid.reload(); }, appendHealthCheckFailures: function(items, hcf){ @@ -101,6 +130,11 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, { text: hcf.description }); } + items.push({ + xtype: 'box', + height: 10, + colspan: 2 + }); } }); From 5bfbe5fe9a10dff9a598d24f354f6f956c27bfcf Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 25 Jan 2014 15:43:13 +0100 Subject: [PATCH 12/22] update svnkit to version 1.8.3-scm1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6cb06cbb21..de1a088ebc 100644 --- a/pom.xml +++ b/pom.xml @@ -431,7 +431,7 @@ 3.2.0.201312181205-r - 1.7.10-scm4 + 1.8.3-scm1 15.0 From 4d6f3df02ad96fab27c07c18550f4140603bf830 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 28 Jan 2014 20:58:06 +0100 Subject: [PATCH 13/22] fix typo in health check url --- .../src/main/java/sonia/scm/repository/DBFormatHealthCheck.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/DBFormatHealthCheck.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/DBFormatHealthCheck.java index c9958f24fb..1f90943e6f 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/DBFormatHealthCheck.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/DBFormatHealthCheck.java @@ -77,7 +77,7 @@ public class DBFormatHealthCheck extends DirectoryHealthCheck /** Field description */ private static final HealthCheckFailure INCOMPATIBLE_DB_FORMAT = new HealthCheckFailure("AnOTx99ex1", "Incompatible DB Format", - "https://bitbucket.org/sdorra/scm-manager/wiki/healtchecks/svn-incompatible-dbformat", + "https://bitbucket.org/sdorra/scm-manager/wiki/healthchecks/svn-incompatible-dbformat", "The subversion db format is incompatible with the svn version used within scm-manager."); /** Field description */ From b05cf39df853d8e40204beb462ebc1b7a4a9e227 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 28 Jan 2014 21:34:31 +0100 Subject: [PATCH 14/22] fix possible concurrent modification exception --- scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java b/scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java index 33765ce8a0..a0baa41034 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java +++ b/scm-dao-xml/src/main/java/sonia/scm/xml/AbstractXmlDAO.java @@ -34,6 +34,7 @@ package sonia.scm.xml; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.collect.ImmutableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -229,7 +230,8 @@ public abstract class AbstractXmlDAO getAll() { - return db.values(); + // avoid concurrent modification exceptions + return ImmutableList.copyOf(db.values()); } /** From 74661789f81d6ca2d9a5db23dba4fde7d7a1d71e Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 29 Jan 2014 12:46:14 +0100 Subject: [PATCH 15/22] fix exception on login, if an external authenticator returns a changed user object --- .../main/java/sonia/scm/security/ScmRealm.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 1234c4eabd..15687cd63f 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java @@ -121,9 +121,8 @@ public class ScmRealm extends AuthorizingRealm */ @Inject public ScmRealm(ScmConfiguration configuration, - LoginAttemptHandler loginAttemptHandler, - AuthorizationCollector collector,UserManager userManager, - GroupManager groupManager, UserDAO userDAO, + LoginAttemptHandler loginAttemptHandler, AuthorizationCollector collector, + UserManager userManager, GroupManager groupManager, UserDAO userDAO, AuthenticationManager authenticator, RepositoryManager manager, Provider requestProvider, Provider responseProvider) @@ -149,8 +148,6 @@ public class ScmRealm extends AuthorizingRealm // set components setPermissionResolver(new RepositoryPermissionResolver()); } - - private final LoginAttemptHandler loginAttemptHandler; //~--- methods -------------------------------------------------------------- @@ -174,7 +171,7 @@ public class ScmRealm extends AuthorizingRealm { throw new UnsupportedTokenException("ScmAuthenticationToken is required"); } - + loginAttemptHandler.beforeAuthentication(authToken); UsernamePasswordToken token = (UsernamePasswordToken) authToken; @@ -198,6 +195,7 @@ public class ScmRealm extends AuthorizingRealm else { loginAttemptHandler.onUnsuccessfulAuthentication(authToken, result); + throw new AccountException("authentication failed"); } @@ -362,7 +360,10 @@ public class ScmRealm extends AuthorizingRealm // modify existing user, copy properties except password and admin if (user.copyProperties(dbUser, false)) { - userManager.modify(dbUser); + user.setLastModified(System.currentTimeMillis()); + UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_MODIFY); + userDAO.modify(user); + UserEventHack.fireEvent(userManager, user, HandlerEvent.MODIFY); } } @@ -547,6 +548,9 @@ public class ScmRealm extends AuthorizingRealm /** Field description */ private final GroupManager groupManager; + /** Field description */ + private final LoginAttemptHandler loginAttemptHandler; + /** Field description */ private final Provider requestProvider; From 387f39bfe180efdfd5426292356fb1d7ec0f0da0 Mon Sep 17 00:00:00 2001 From: Sergey Averkiev Date: Wed, 29 Jan 2014 16:26:57 +0400 Subject: [PATCH 16/22] Add feature to set custom realm description --- .../sonia/scm/config/ScmConfiguration.java | 31 ++++++++++++++ .../main/java/sonia/scm/util/HttpUtil.java | 41 +++++++++++++++++-- .../web/filter/BasicAuthenticationFilter.java | 2 +- .../scm/web/filter/PermissionFilter.java | 2 +- .../js/config/sonia.config.scmconfigpanel.js | 8 ++++ 5 files changed, 78 insertions(+), 6 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index 40f662969f..d65589db82 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -44,6 +44,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.ConfigChangedListener; import sonia.scm.ListenerSupport; import sonia.scm.event.ScmEventBus; +import sonia.scm.util.HttpUtil; import sonia.scm.xml.XmlSetStringAdapter; //~--- JDK imports ------------------------------------------------------------ @@ -157,6 +158,7 @@ public class ScmConfiguration */ public void load(ScmConfiguration other) { + this.realmDescription = other.realmDescription; this.dateFormat = other.dateFormat; this.pluginUrl = other.pluginUrl; this.anonymousAccessEnabled = other.anonymousAccessEnabled; @@ -231,6 +233,17 @@ public class ScmConfiguration return baseUrl; } + /** + * Returns the realm description. + * + * + * @return realm description + */ + public String getRealmDescription() + { + return realmDescription; + } + /** * Returns the date format for the user interface. This format is a * JavaScript date format, from the library moment.js. @@ -516,6 +529,17 @@ public class ScmConfiguration this.baseUrl = baseUrl; } + /** + * Sets the realm description. + * + * + * @param realmDescription + */ + public void setRealmDescription(String realmDescription) + { + this.realmDescription = realmDescription; + } + /** * Sets the date format for the ui. * @@ -824,6 +848,13 @@ public class ScmConfiguration /** Field description */ private boolean disableGroupingGrid = false; + /** + * + * Authentication realm for basic authentication. + * + */ + private String realmDescription = HttpUtil.AUTHENTICATION_REALM; + /** * JavaScript date format from moment.js * @see http://momentjs.com/docs/#/parsing/ diff --git a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java index f1ce453cc1..69f913d74d 100644 --- a/scm-core/src/main/java/sonia/scm/util/HttpUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/HttpUtil.java @@ -416,7 +416,7 @@ public final class HttpUtil throws IOException { - sendUnauthorized(null, response); + sendUnauthorized(null, response, AUTHENTICATION_REALM); } /** @@ -427,17 +427,50 @@ public final class HttpUtil * @param response http response * * @throws IOException - * - * @since 1.19 */ public static void sendUnauthorized(HttpServletRequest request, HttpServletResponse response) throws IOException + { + sendUnauthorized(request, response, AUTHENTICATION_REALM); + } + + /** + * Send an unauthorized header back to the client + * + * + * @param response - the http response + * @param realmDescription - realm description + * + * @throws IOException + */ + public static void sendUnauthorized(HttpServletResponse response, String realmDescription) + throws IOException + { + sendUnauthorized(null, response, realmDescription); + } + + /** + * Send an unauthorized header back to the client + * + * + * @param request http request + * @param response http response + * @param realmDescription realm description + * + * @throws IOException + * + * @since 1.19 + */ + public static void sendUnauthorized(HttpServletRequest request, + HttpServletResponse response, + String realmDescription) + throws IOException { if ((request == null) ||!isWUIRequest(request)) { response.setHeader(HEADER_WWW_AUTHENTICATE, - "Basic realm=\"".concat(AUTHENTICATION_REALM).concat("\"")); + "Basic realm=\"".concat(realmDescription).concat("\"")); } else if (logger.isTraceEnabled()) diff --git a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java index 5a81a7c53c..44801de4d2 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java @@ -216,7 +216,7 @@ public class BasicAuthenticationFilter extends AutoLoginFilter if (Strings.isNullOrEmpty(authentication)) { - HttpUtil.sendUnauthorized(request, response); + HttpUtil.sendUnauthorized(request, response, configuration.getRealmDescription()); } else { diff --git a/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java index da3a8ead39..07051e5154 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java @@ -262,7 +262,7 @@ public abstract class PermissionFilter extends HttpFilter } else { - HttpUtil.sendUnauthorized(response); + HttpUtil.sendUnauthorized(response, configuration.getRealmDescription()); } } diff --git a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js index 72b85e2e8a..9f80b5eed8 100644 --- a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js +++ b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js @@ -34,6 +34,7 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ titleText: 'General Settings', servnameText: 'Servername', + realmDescriptionText: 'Realm description', dateFormatText: 'Date format', enableForwardingText: 'Enable forwarding (mod_proxy)', forwardPortText: 'Forward Port', @@ -67,6 +68,7 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ // help servernameHelpText: 'The name of this server. This name will be part of the repository url.', + realmDescriptionHelpText: 'Enter authentication realm description', // TODO i18n dateFormatHelpText: 'Moments date format. Please have a look at \n\ http://momentjs.com/docs/#/displaying/format/.
\n\ @@ -129,6 +131,12 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ name: 'enableRepositoryArchive', inputValue: 'true', helpText: this.enableRepositoryArchiveHelpText + },{ + xtype: 'textfield', + fieldLabel: this.realmDescriptionText, + name: 'realmDescription', + allowBlank: false, + helpText: this.realmDescriptionHelpText },{ xtype: 'textfield', fieldLabel: this.dateFormatText, From 0271a5f5e9ddad7f55b92419d71bfbbf319e98da Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 30 Jan 2014 20:59:33 +0100 Subject: [PATCH 17/22] fix JsonMappingException on repository update --- .../webapp/resources/js/repository/sonia.repository.grid.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js index b6db7fb59f..99293a225d 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js @@ -99,7 +99,8 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, { },{ name: 'archived' },{ - name: 'healthCheckFailures' + name: 'healthCheckFailures', + defaultValue: null }] }), sortInfo: { From cb3ad8f19be860b01ce61d80f4e666a68c1e828b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 16 Feb 2014 15:02:20 +0100 Subject: [PATCH 18/22] fix possible npe on authentication, see issue #531 --- .../rest/IllegalArgumentExceptionMapper.java | 64 +++++++++++++++++++ .../resources/AuthenticationResource.java | 8 +++ 2 files changed, 72 insertions(+) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/IllegalArgumentExceptionMapper.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/IllegalArgumentExceptionMapper.java b/scm-webapp/src/main/java/sonia/scm/api/rest/IllegalArgumentExceptionMapper.java new file mode 100644 index 0000000000..32b6a02bc6 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/IllegalArgumentExceptionMapper.java @@ -0,0 +1,64 @@ +/** + * 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.api.rest; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +/** + * + * @author Sebastian Sdorra + * @since 1.36 + */ +@Provider +public class IllegalArgumentExceptionMapper + implements ExceptionMapper +{ + + /** + * Method description + * + * + * @param exception + * + * @return + */ + @Override + public Response toResponse(IllegalArgumentException exception) + { + return Response.status(Status.BAD_REQUEST).build(); + } +} 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 55ba98663b..b7f02187cb 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 @@ -35,6 +35,8 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.inject.Inject; @@ -153,6 +155,7 @@ public class AuthenticationResource *
*
    *
  • 200 success
  • + *
  • 400 bad request, required parameter is missing.
  • *
  • 401 unauthorized, the specified username or password is wrong
  • *
  • 500 internal server error
  • *
@@ -172,6 +175,11 @@ public class AuthenticationResource @FormParam("password") String password, @FormParam("rememberMe") @DefaultValue("false") boolean rememberMe) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(username), + "username parameter is required"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(password), + "password parameter is required"); + Response response; Subject subject = SecurityUtils.getSubject(); From 7d198a2d9546b0e2cc1601f0b87bfdb3857fa7f7 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 16 Feb 2014 15:41:48 +0100 Subject: [PATCH 19/22] added option to skip failed authenticators --- .../sonia/scm/config/ScmConfiguration.java | 122 +++++++++++------- .../security/ChainAuthenticatonManager.java | 37 ++++-- .../js/config/sonia.config.scmconfigpanel.js | 9 ++ .../src/main/webapp/resources/js/i18n/de.js | 6 +- .../ChainAuthenticationManagerTest.java | 46 ++++++- 5 files changed, 160 insertions(+), 60 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index d65589db82..580c666ed0 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -233,17 +233,6 @@ public class ScmConfiguration return baseUrl; } - /** - * Returns the realm description. - * - * - * @return realm description - */ - public String getRealmDescription() - { - return realmDescription; - } - /** * Returns the date format for the user interface. This format is a * JavaScript date format, from the library moment.js. @@ -375,6 +364,17 @@ public class ScmConfiguration return proxyUser; } + /** + * Returns the realm description. + * + * + * @return realm description + */ + public String getRealmDescription() + { + return realmDescription; + } + /** * Returns the servername of the SCM-Manager host. * @@ -482,6 +482,19 @@ public class ScmConfiguration return forceBaseUrl; } + /** + * Method description + * + * + * @return + * + * @since 1.36 + */ + public boolean isSkipFailedAuthenticators() + { + return skipFailedAuthenticators; + } + //~--- set methods ---------------------------------------------------------- /** @@ -529,17 +542,6 @@ public class ScmConfiguration this.baseUrl = baseUrl; } - /** - * Sets the realm description. - * - * - * @param realmDescription - */ - public void setRealmDescription(String realmDescription) - { - this.realmDescription = realmDescription; - } - /** * Sets the date format for the ui. * @@ -733,6 +735,17 @@ public class ScmConfiguration this.proxyUser = proxyUser; } + /** + * Sets the realm description. + * + * + * @param realmDescription + */ + public void setRealmDescription(String realmDescription) + { + this.realmDescription = realmDescription; + } + /** * Method description * @@ -745,6 +758,19 @@ public class ScmConfiguration this.servername = servername; } + /** + * Method description + * + * + * @param skipFailedAuthenticators + * + * @since 1.36 + */ + public void setSkipFailedAuthenticators(boolean skipFailedAuthenticators) + { + this.skipFailedAuthenticators = skipFailedAuthenticators; + } + /** * Method description * @@ -790,21 +816,6 @@ public class ScmConfiguration @XmlElement(name = "login-attempt-limit") private int loginAttemptLimit = -1; - /** - * Login attempt timeout. - * - * @since 1.34 - */ - @XmlElement(name = "login-attempt-limit-timeout") - private long loginAttemptLimitTimeout = TimeUnit.MINUTES.toSeconds(5l); - - /** Field description */ - private boolean enableProxy = false; - - /** Field description */ - @XmlElement(name = "plugin-url") - private String pluginUrl = DEFAULT_PLUGINURL; - /** glob patterns for urls which are excluded from proxy */ @XmlElement(name = "proxy-excludes") @XmlJavaTypeAdapter(XmlSetStringAdapter.class) @@ -825,10 +836,33 @@ public class ScmConfiguration /** @deprecated use {@link #baseUrl} */ private String servername = "localhost"; + /** + * Skip failed authenticators. + * + * @since 1.36 + */ + @XmlElement(name = "skip-failed-authenticators") + private boolean skipFailedAuthenticators = false; + + /** Field description */ + @XmlElement(name = "plugin-url") + private String pluginUrl = DEFAULT_PLUGINURL; + + /** + * Login attempt timeout. + * + * @since 1.34 + */ + @XmlElement(name = "login-attempt-limit-timeout") + private long loginAttemptLimitTimeout = TimeUnit.MINUTES.toSeconds(5l); + /** @deprecated use {@link #baseUrl} and {@link #forceBaseUrl} */ @Deprecated private boolean enableSSL = false; + /** Field description */ + private boolean enableProxy = false; + /** @deprecated use {@link #baseUrl} */ @Deprecated private boolean enablePortForward = false; @@ -837,6 +871,13 @@ public class ScmConfiguration @Deprecated private int sslPort = 8181; + /** + * + * Authentication realm for basic authentication. + * + */ + private String realmDescription = HttpUtil.AUTHENTICATION_REALM; + /** Configuration change listeners */ @XmlTransient private Set listeners = @@ -848,13 +889,6 @@ public class ScmConfiguration /** Field description */ private boolean disableGroupingGrid = false; - /** - * - * Authentication realm for basic authentication. - * - */ - private String realmDescription = HttpUtil.AUTHENTICATION_REALM; - /** * JavaScript date format from moment.js * @see http://momentjs.com/docs/#/parsing/ diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java b/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java index e7ef7c43af..f768a4d044 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java @@ -47,6 +47,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.SCMContextProvider; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; +import sonia.scm.config.ScmConfiguration; import sonia.scm.security.EncryptionHandler; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -87,21 +88,24 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager * * * + * + * @param configuration * @param userManager * @param authenticationHandlerSet * @param encryptionHandler * @param cacheManager - * @param authenticationListenerProvider * @param authenticationListeners */ @Inject - public ChainAuthenticatonManager(UserManager userManager, + public ChainAuthenticatonManager(ScmConfiguration configuration, + UserManager userManager, Set authenticationHandlerSet, EncryptionHandler encryptionHandler, CacheManager cacheManager, Set authenticationListeners) { AssertUtil.assertIsNotEmpty(authenticationHandlerSet); AssertUtil.assertIsNotNull(cacheManager); + this.configuration = configuration; this.authenticationHandlers = sort(userManager, authenticationHandlerSet); this.encryptionHandler = encryptionHandler; this.cache = cacheManager.getCache(String.class, @@ -200,6 +204,22 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager } } + /** + * Method description + * + * + * @param result + * + * @return + */ + boolean stopChain(AuthenticationResult result) + { + return (result != null) && (result.getState() != null) + && (result.getState().isSuccessfully() + || ((result.getState() == AuthenticationState.FAILED) + &&!configuration.isSkipFailedAuthenticators())); + } + /** * Method description * @@ -240,9 +260,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager authenticator.getClass().getName(), result); } - if ((result != null) && (result.getState() != null) - && (result.getState().isSuccessfully() - || (result.getState() == AuthenticationState.FAILED))) + if (stopChain(result)) { if (result.getState().isSuccessfully() && (result.getUser() != null)) { @@ -378,11 +396,14 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager //~--- fields --------------------------------------------------------------- /** Field description */ - private List authenticationHandlers; + private final List authenticationHandlers; /** Field description */ - private Cache cache; + private final Cache cache; /** Field description */ - private EncryptionHandler encryptionHandler; + private final ScmConfiguration configuration; + + /** Field description */ + private final EncryptionHandler encryptionHandler; } diff --git a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js index 9f80b5eed8..29efa3fa00 100644 --- a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js +++ b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js @@ -51,6 +51,7 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ errorSubmitMsgText: 'Could not submit config.', // TODO i18n + skipFailedAuthenticatorsText: 'Skip failed authenticators', loginAttemptLimitText: 'Login Attempt Limit', loginAttemptLimitTimeoutText: 'Login Attempt Limit Timeout', @@ -85,6 +86,8 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ adminUsersHelpText: 'Comma seperated list of users with admin permissions.', // TODO i18n + skipFailedAuthenticatorsHelpText: 'Do not stop the authentication chain, \n\ + if an authenticator finds the user but fails to authenticate the user.', loginAttemptLimitHelpText: 'Maximum allowed login attempts. Use -1 to disable the login attempt limit.', loginAttemptLimitTimeoutHelpText: 'Timeout in seconds for users which are temporary disabled,\ because of too many failed login attempts.', @@ -157,6 +160,12 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ name: 'anonymousAccessEnabled', inputValue: 'true', helpText: this.allowAnonymousAccessHelpText + },{ + xtype: 'checkbox', + fieldLabel: this.skipFailedAuthenticatorsText, + name: 'skip-failed-authenticators', + inputValue: 'true', + helpText: this.skipFailedAuthenticatorsHelpText },{ xtype: 'numberfield', fieldLabel: this.loginAttemptLimitText, diff --git a/scm-webapp/src/main/webapp/resources/js/i18n/de.js b/scm-webapp/src/main/webapp/resources/js/i18n/de.js index 40b6b6dca6..6c8e00f8e4 100644 --- a/scm-webapp/src/main/webapp/resources/js/i18n/de.js +++ b/scm-webapp/src/main/webapp/resources/js/i18n/de.js @@ -40,7 +40,7 @@ if (Ext.form.VTypes){ passwordText: 'Die Passwörter stimmen nicht überein!', nameTest: 'Der Name ist invalid.', usernameText: 'Der Benutzername ist invalid.', - repositoryNameText: 'Der Name des Repositorys ist ungültig.', + repositoryNameText: 'Der Name des Repositorys ist ungültig.' }); } @@ -349,6 +349,10 @@ if (Sonia.config.ScmConfigPanel){ adminGroupsHelpText: 'Komma getrennte Liste von Gruppen mit Administrationsrechten.', adminUsersHelpText: 'Komma getrennte Liste von Benutzern mit Administrationsrechten.', + skipFailedAuthenticatorsText: 'Überspringe fehlgeschlagene Authentifizierer', + skipFailedAuthenticatorsHelpText: 'Setzt die Authentifizierungs-Kette fort,\n\ + auch wenn ein ein Authentifizierer einen Benutzer gefunden hat,\n\ + diesen aber nicht Authentifizieren kann.', loginAttemptLimitText: 'Login Attempt Limit', loginAttemptLimitTimeoutText: 'Login Attempt Limit Timeout', loginAttemptLimitHelpText: 'Maximale Anzahl gescheiterte Loginversuche. Der Wert -1 deaktiviert die Begrenzung.', diff --git a/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java b/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java index d35068db82..ab4e68e256 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java @@ -42,6 +42,7 @@ import org.junit.Test; import sonia.scm.AbstractTestBase; import sonia.scm.SCMContextProvider; import sonia.scm.cache.MapCacheManager; +import sonia.scm.config.ScmConfiguration; import sonia.scm.security.MessageDigestEncryptionHandler; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -137,7 +138,7 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase SingleUserAuthenticaionHandler a2 = new SingleUserAuthenticaionHandler("a2", trillian); - manager = createManager("a2", a1, a2); + manager = createManager("a2", false, a1, a2); AuthenticationResult result = manager.authenticate(request, response, trillian.getName(), "trillian123"); @@ -147,6 +148,24 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase assertEquals("a2", result.getUser().getType()); } + /** + * Method description + * + */ + @Test + public void testStopChain() + { + ChainAuthenticatonManager cam = createManager("", false); + + assertTrue(cam.stopChain(new AuthenticationResult(perfect))); + assertTrue(cam.stopChain(AuthenticationResult.FAILED)); + assertFalse(cam.stopChain(AuthenticationResult.NOT_FOUND)); + cam = createManager("", true); + assertTrue(cam.stopChain(new AuthenticationResult(perfect))); + assertFalse(cam.stopChain(AuthenticationResult.FAILED)); + assertFalse(cam.stopChain(AuthenticationResult.NOT_FOUND)); + } + /** * Method description * @@ -199,7 +218,7 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase trillian = UserTestData.createTrillian(); trillian.setPassword("trillian123"); - return createManager("", + return createManager("", false, new SingleUserAuthenticaionHandler("perfectsType", perfect), new SingleUserAuthenticaionHandler("trilliansType", trillian)); } @@ -209,20 +228,33 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase * * * @param defaultType + * @param skipFailedAuthenticators * @param handlers * * @return */ private ChainAuthenticatonManager createManager(String defaultType, - AuthenticationHandler... handlers) + boolean skipFailedAuthenticators, AuthenticationHandler... handlers) { + if ( handlers == null || handlers.length == 0 ){ + //J- + handlers = new AuthenticationHandler[]{ + new SingleUserAuthenticaionHandler("perfectsType", perfect), + new SingleUserAuthenticaionHandler("trilliansType", trillian) + }; + //J+ + } + ScmConfiguration configuration = new ScmConfiguration(); + + configuration.setSkipFailedAuthenticators(skipFailedAuthenticators); + Set handlerSet = ImmutableSet.copyOf(handlers); UserManager userManager = mock(UserManager.class); when(userManager.getDefaultType()).thenReturn(defaultType); - manager = new ChainAuthenticatonManager(userManager, handlerSet, - new MessageDigestEncryptionHandler(), new MapCacheManager(), + manager = new ChainAuthenticatonManager(configuration, userManager, + handlerSet, new MessageDigestEncryptionHandler(), new MapCacheManager(), Collections.EMPTY_SET); manager.init(contextProvider); @@ -328,10 +360,10 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase //~--- fields ------------------------------------------------------------- /** Field description */ - private String type; + private final String type; /** Field description */ - private User user; + private final User user; } From 1e802a63341dd975eea1f9384ef532f97e56c3c1 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sun, 16 Feb 2014 15:51:05 +0100 Subject: [PATCH 20/22] fixed small bug and javadoc --- .../main/java/sonia/scm/config/ScmConfiguration.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index 580c666ed0..35b13dd902 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -174,6 +174,7 @@ public class ScmConfiguration this.baseUrl = other.baseUrl; this.disableGroupingGrid = other.disableGroupingGrid; this.enableRepositoryArchive = other.enableRepositoryArchive; + this.skipFailedAuthenticators = other.skipFailedAuthenticators; this.loginAttemptLimit = other.loginAttemptLimit; this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout; @@ -483,10 +484,10 @@ public class ScmConfiguration } /** - * Method description + * Returns true if failed authenticators are skipped. * * - * @return + * @return true if failed authenticators are skipped * * @since 1.36 */ @@ -759,10 +760,10 @@ public class ScmConfiguration } /** - * Method description + * If set to true the authentication chain is not stopped, if an + * authenticator finds the user but fails to authenticate the user. * - * - * @param skipFailedAuthenticators + * @param skipFailedAuthenticators true to skip failed authenticators * * @since 1.36 */ From 42d271b353ed020f753ada4df67f81f986e36299 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 18 Feb 2014 20:46:44 +0100 Subject: [PATCH 21/22] update slf4j to version 1.7.6 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index de1a088ebc..b16e0c3303 100644 --- a/pom.xml +++ b/pom.xml @@ -417,7 +417,7 @@ 4.11 - 1.7.5 + 1.7.6 1.0.13 2.5 3.0 From 32fa25a322f578265f3521bd0a77b340d89df0f0 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 18 Feb 2014 20:47:50 +0100 Subject: [PATCH 22/22] update logback to version 1.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b16e0c3303..cdd8d13a67 100644 --- a/pom.xml +++ b/pom.xml @@ -418,7 +418,7 @@ 1.7.6 - 1.0.13 + 1.1.1 2.5 3.0 1.18