From 9cb8ce5a50e42e31a1dc4d8cfc818bc6f387871a Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 20 Nov 2010 21:14:11 +0100 Subject: [PATCH] added permission check for git --- .../scm/repository/GitRepositoryHandler.java | 48 +++-- .../sonia/scm/web/GitPermissionFilter.java | 135 +++++++++++++ .../main/java/sonia/scm/web/GitWebPlugin.java | 10 +- .../sonia/scm/repository/PermissionUtil.java | 112 +++++++++++ .../scm/repository/PermissionUtilTest.java | 126 ++++++++++++ .../scm/web/filter/PermissionFilter.java | 180 ++++++++++++++++++ scm-webapp/pom.xml | 2 +- 7 files changed, 599 insertions(+), 14 deletions(-) create mode 100644 plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java create mode 100644 scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java create mode 100644 scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java create mode 100644 scm-web-api/src/main/java/sonia/scm/web/filter/PermissionFilter.java diff --git a/plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java b/plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java index 729bce8b4e..5960d9dd8d 100644 --- a/plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java +++ b/plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitRepositoryHandler.java @@ -66,6 +66,31 @@ public class GitRepositoryHandler //~--- get methods ---------------------------------------------------------- + /** + * TODO dont use getAll + * + * + * @param name + * + * @return + */ + public Repository getByName(String name) + { + Repository repository = null; + + for (Repository r : getAll()) + { + if (r.getName().equals(name)) + { + repository = r; + + break; + } + } + + return repository; + } + /** * Method description * @@ -98,6 +123,18 @@ public class GitRepositoryHandler directory).readEnvironment().findGitDir().build().create(true); } + /** + * Method description + * + * + * @return + */ + @Override + protected GitConfig createInitialConfig() + { + return new GitConfig(); + } + //~--- get methods ---------------------------------------------------------- /** @@ -111,15 +148,4 @@ public class GitRepositoryHandler { return GitConfig.class; } - - @Override - protected GitConfig createInitialConfig() - { - return new GitConfig(); - } - - - - //~--- fields --------------------------------------------------------------- - } diff --git a/plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java b/plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java new file mode 100644 index 0000000000..8277a0722b --- /dev/null +++ b/plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.Repository; +import sonia.scm.web.filter.PermissionFilter; +import sonia.scm.web.security.SecurityContext; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public class GitPermissionFilter extends PermissionFilter +{ + + /** Field description */ + public static final String PATTERN_WRITEREQUEST = "git-receive-pack"; + + /** Field description */ + public static final Pattern PATTERN_REPOSITORYNAME = + Pattern.compile("/[^/]+/([^/]+)(?:/.*)?"); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * + * @param securityContextProvider + * @param handler + */ + @Inject + public GitPermissionFilter(Provider securityContextProvider, + GitRepositoryHandler handler) + { + super(securityContextProvider); + this.handler = handler; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * + * @return + */ + @Override + protected Repository getRepository(HttpServletRequest request) + { + Repository repository = null; + String uri = request.getRequestURI(); + + uri = uri.substring(request.getContextPath().length()); + + Matcher m = PATTERN_REPOSITORYNAME.matcher(uri); + + if (m.matches()) + { + String repositoryname = m.group(1); + + repository = handler.getByName(repositoryname); + } + + return repository; + } + + /** + * Method description + * + * + * @param request + * + * @return + */ + @Override + protected boolean isWriteRequest(HttpServletRequest request) + { + return request.getRequestURI().endsWith(PATTERN_WRITEREQUEST); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private GitRepositoryHandler handler; +} diff --git a/plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitWebPlugin.java b/plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitWebPlugin.java index e568f24d31..5f588f4918 100644 --- a/plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitWebPlugin.java +++ b/plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitWebPlugin.java @@ -29,6 +29,8 @@ * */ + + package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- @@ -47,6 +49,9 @@ import sonia.scm.web.plugin.ScmWebPluginContext; public class GitWebPlugin implements ScmWebPlugin { + /** Field description */ + public static final String PATTERN_GIT = "/git/*"; + /** Field description */ public static final String SCRIPT = "/sonia/scm/git.config.js"; @@ -80,8 +85,9 @@ public class GitWebPlugin implements ScmWebPlugin @Override protected void configureServlets() { - filter("/git/*").through(BasicAuthenticationFilter.class); - serve("/git/*").with(ScmGitServlet.class); + filter(PATTERN_GIT).through(BasicAuthenticationFilter.class); + filter(PATTERN_GIT).through(GitPermissionFilter.class); + serve(PATTERN_GIT).with(ScmGitServlet.class); } }); } diff --git a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java new file mode 100644 index 0000000000..54aef232fe --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java @@ -0,0 +1,112 @@ +/** + * 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 sonia.scm.user.User; +import sonia.scm.util.AssertUtil; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.List; + +/** + * + * @author Sebastian Sdorra + */ +public class PermissionUtil +{ + + /** + * Method description + * + * + * @param repository + * @param user + * @param write + */ + public static void assertPermission(Repository repository, User user, + boolean write) + { + if (!hasPermission(repository, user, write)) + { + throw new IllegalStateException("action denied"); + } + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param repository + * @param user + * @param write + * + * @return + */ + public static boolean hasPermission(Repository repository, User user, + boolean write) + { + String username = user.getName(); + + AssertUtil.assertIsNotEmpty(username); + + boolean result = false; + List permissions = repository.getPermissions(); + + if (permissions != null) + { + for (Permission p : permissions) + { + if (!p.isGroupPermission()) + { + String name = p.getName(); + + if ((name != null) && name.equalsIgnoreCase(username) + && (!write || p.isWriteable())) + { + result = true; + + break; + } + } + } + } + + return result; + } +} diff --git a/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java b/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java new file mode 100644 index 0000000000..21d8a05262 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java @@ -0,0 +1,126 @@ +/** + * 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 org.junit.Test; + +import sonia.scm.user.User; + +import static org.junit.Assert.*; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Arrays; + +/** + * + * @author Sebastian Sdorra + */ +public class PermissionUtilTest +{ + + /** + * Constructs ... + * + */ + public PermissionUtilTest() + { + repository = new Repository(); + + Permission[] permissions = new Permission[] { new Permission("dent", false), + new Permission("perfect", true) }; + + repository.setPermissions(Arrays.asList(permissions)); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Test(expected = IllegalStateException.class) + public void assertFailedPermissionTest() + { + PermissionUtil.assertPermission(repository, dent, true); + } + + /** + * Method description + * + */ + @Test + public void assertPermissionTest() + { + PermissionUtil.hasPermission(repository, dent, false); + PermissionUtil.hasPermission(repository, perfect, false); + PermissionUtil.hasPermission(repository, perfect, true); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + */ + @Test + public void hasPermissionTest() + { + assertTrue(PermissionUtil.hasPermission(repository, dent, false)); + assertTrue(PermissionUtil.hasPermission(repository, perfect, false)); + assertTrue(PermissionUtil.hasPermission(repository, perfect, true)); + assertFalse(PermissionUtil.hasPermission(repository, dent, true)); + assertFalse(PermissionUtil.hasPermission(repository, slarti, true)); + assertFalse(PermissionUtil.hasPermission(repository, slarti, false)); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private User dent = new User("dent", "Arthur Dent", + "arthur.dent@hitchhiker.com"); + + /** Field description */ + private User perfect = new User("perfect", "Ford Prefect", + "ford.perfect@hitchhiker.com"); + + /** Field description */ + private Repository repository; + + /** Field description */ + private User slarti = new User("slarti", "Slartibartfaß", + "Slartibartfass@hitchhiker.com"); +} diff --git a/scm-web-api/src/main/java/sonia/scm/web/filter/PermissionFilter.java b/scm-web-api/src/main/java/sonia/scm/web/filter/PermissionFilter.java new file mode 100644 index 0000000000..aa39a88ad9 --- /dev/null +++ b/scm-web-api/src/main/java/sonia/scm/web/filter/PermissionFilter.java @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.filter; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Provider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.repository.PermissionUtil; +import sonia.scm.repository.Repository; +import sonia.scm.user.User; +import sonia.scm.util.AssertUtil; +import sonia.scm.web.security.SecurityContext; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +public abstract class PermissionFilter extends HttpFilter +{ + + /** the logger for PermissionFilter */ + private static final Logger logger = + LoggerFactory.getLogger(PermissionFilter.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param securityContextProvider + */ + public PermissionFilter(Provider securityContextProvider) + { + this.securityContextProvider = securityContextProvider; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * + * @return + */ + protected abstract Repository getRepository(HttpServletRequest request); + + /** + * Method description + * + * + * @param request + * + * @return + */ + protected abstract boolean isWriteRequest(HttpServletRequest request); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * @param response + * @param chain + * + * @throws IOException + * @throws ServletException + */ + @Override + protected void doFilter(HttpServletRequest request, + HttpServletResponse response, FilterChain chain) + throws IOException, ServletException + { + SecurityContext securityContext = securityContextProvider.get(); + + AssertUtil.assertIsNotNull(securityContext); + + User user = securityContext.getUser(); + + if (user != null) + { + Repository repository = getRepository(request); + + if (repository != null) + { + boolean writeRequest = isWriteRequest(request); + + if (PermissionUtil.hasPermission(repository, securityContext.getUser(), + writeRequest)) + { + chain.doFilter(request, response); + } + else + { + if (logger.isInfoEnabled()) + { + logger.info("{} access to repostitory {} for user {} denied", + new Object[] { writeRequest + ? "write" + : "read", repository.getName(), + user.getName() }); + } + + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("repository not found"); + } + + response.sendError(HttpServletResponse.SC_NOT_FOUND); + } + } + else + { + if (logger.isDebugEnabled()) + { + logger.debug("user in not authenticated"); + } + + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + protected Provider securityContextProvider; +} diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 7aaa4222d7..95e1eb42c3 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -112,7 +112,7 @@ org.mortbay.jetty maven-jetty-plugin - 6.1.25 + 6.1.26 9966 foo