diff --git a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java index ae7b8d33b2..6d77ed8617 100644 --- a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java @@ -113,7 +113,7 @@ public class PermissionUtil @Deprecated public static void assertPermission(Repository repository, PermissionType pt) { - if (!hasPermission(repository, pt)) + if (!hasPermission(null, repository, pt)) { throw new ScmSecurityException("action denied"); } @@ -136,7 +136,7 @@ public class PermissionUtil public static boolean hasPermission(Repository repository, Provider securityContextProvider, PermissionType pt) { - return hasPermission(repository, securityContextProvider.get(), pt); + return hasPermission(null, repository, pt); } /** @@ -154,24 +154,23 @@ public class PermissionUtil public static boolean hasPermission(Repository repository, WebSecurityContext securityContext, PermissionType pt) { - return hasPermission(repository, pt); + return hasPermission(null, repository, pt); } /** * Method description * * + * @param configuration * @param repository - * @param securityContext * @param pt * * @return - * @since 1.21 * - * @deprecated + * @since 1.21 */ - @Deprecated - public static boolean hasPermission(Repository repository, PermissionType pt) + public static boolean hasPermission(ScmConfiguration configuration, + Repository repository, PermissionType pt) { boolean result = false; @@ -179,7 +178,6 @@ public class PermissionUtil if (subject.isAuthenticated()) { - String username = subject.getPrincipal().toString(); AssertUtil.assertIsNotEmpty(username); @@ -203,6 +201,14 @@ public class PermissionUtil } } } + else + { + + // check anonymous access + result = (configuration != null) + && configuration.isAnonymousAccessEnabled() + && repository.isPublicReadable() && (pt == PermissionType.READ); + } return result; } @@ -252,7 +258,7 @@ public class PermissionUtil } else { - permitted = PermissionUtil.hasPermission(repository, + permitted = PermissionUtil.hasPermission(configuration, repository, PermissionType.WRITE); } diff --git a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java index c98e96dedf..eb1371ddd7 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/RepositoryServiceFactory.java @@ -40,20 +40,19 @@ import com.google.common.base.Strings; import com.google.inject.Inject; import com.google.inject.Singleton; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.Subject; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.HandlerEvent; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; +import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.BlameResult; import sonia.scm.repository.Branches; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.PermissionType; +import sonia.scm.repository.PermissionUtil; import sonia.scm.repository.PostReceiveRepositoryHook; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.Repository; @@ -65,7 +64,6 @@ import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.Tags; import sonia.scm.repository.spi.RepositoryServiceProvider; import sonia.scm.repository.spi.RepositoryServiceResolver; -import sonia.scm.security.RepositoryPermission; import sonia.scm.security.ScmSecurityException; //~--- JDK imports ------------------------------------------------------------ @@ -135,12 +133,38 @@ public final class RepositoryServiceFactory * @param securityContextProvider provider for the current security context * @param resolvers a set of {@link RepositoryServiceResolver} * @param preProcessorUtil helper object for pre processor handling + * + * @deprecated */ - @Inject + @Deprecated public RepositoryServiceFactory(CacheManager cacheManager, RepositoryManager repositoryManager, Set resolvers, PreProcessorUtil preProcessorUtil) { + this(null, cacheManager, repositoryManager, resolvers, preProcessorUtil); + } + + /** + * Constructs a new {@link RepositoryServiceFactory}. This constructor + * should not be called manually, it should only be used by the injection + * container. + * + * + * @param configuration configuration + * @param cacheManager cache manager + * @param repositoryManager manager for repositories + * @param securityContextProvider provider for the current security context + * @param resolvers a set of {@link RepositoryServiceResolver} + * @param preProcessorUtil helper object for pre processor handling + * + * @since 1.21 + */ + @Inject + public RepositoryServiceFactory(ScmConfiguration configuration, + CacheManager cacheManager, RepositoryManager repositoryManager, + Set resolvers, PreProcessorUtil preProcessorUtil) + { + this.configuration = configuration; this.cacheManager = cacheManager; this.repositoryManager = repositoryManager; this.resolvers = resolvers; @@ -249,10 +273,8 @@ public final class RepositoryServiceFactory Preconditions.checkNotNull(repository, "repository is required"); // check for read permissions of current user - Subject subject = SecurityUtils.getSubject(); - - if (!subject.isPermitted(new RepositoryPermission(repository, - PermissionType.READ))) + if (!PermissionUtil.hasPermission(configuration, repository, + PermissionType.READ)) { throw new ScmSecurityException("read permission are required"); } @@ -408,6 +430,9 @@ public final class RepositoryServiceFactory /** Field description */ private CacheManager cacheManager; + /** scm-manager configuration */ + private ScmConfiguration configuration; + /** Field description */ private PreProcessorUtil preProcessorUtil; 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 9ebe901a94..0edeb044bc 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 @@ -35,6 +35,7 @@ package sonia.scm.web.filter; //~--- non-JDK imports -------------------------------------------------------- +import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; @@ -45,6 +46,8 @@ import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.SCMContext; +import sonia.scm.config.ScmConfiguration; import sonia.scm.security.ScmAuthenticationToken; import sonia.scm.user.User; import sonia.scm.util.HttpUtil; @@ -85,12 +88,6 @@ public class BasicAuthenticationFilter extends HttpFilter //~--- constructors --------------------------------------------------------- - /** - * Constructs ... - * @since 1.21 - */ - public BasicAuthenticationFilter() {} - /** * Constructs ... * @@ -102,6 +99,19 @@ public class BasicAuthenticationFilter extends HttpFilter public BasicAuthenticationFilter( Provider securityContextProvider) {} + /** + * Constructs a new basic authenticaton filter + * + * @param configuration scm-manager global configuration + * + * @since 1.21 + */ + @Inject + public BasicAuthenticationFilter(ScmConfiguration configuration) + { + this.configuration = configuration; + } + //~--- methods -------------------------------------------------------------- /** @@ -155,6 +165,13 @@ public class BasicAuthenticationFilter extends HttpFilter user = subject.getPrincipals().oneByType(User.class); } + else if ((configuration != null) + && configuration.isAnonymousAccessEnabled()) + { + user = new User(SCMContext.USER_ANONYMOUS, "SCM Anonymous", + "scm-anonymous@scm-manager.com"); + + } if (user == null) { @@ -258,4 +275,9 @@ public class BasicAuthenticationFilter extends HttpFilter return user; } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private ScmConfiguration configuration; } 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 1eefebe505..1d419d3cdb 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 @@ -45,7 +45,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.ArgumentIsInvalidException; -import sonia.scm.SCMContext; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.PermissionType; import sonia.scm.repository.PermissionUtil; @@ -80,12 +79,11 @@ public abstract class PermissionFilter extends HttpFilter //~--- constructors --------------------------------------------------------- /** - * Constructs ... - * - * - * - * @param configuration - * @param securityContextProvider + * Constructs a new permission filter + * + * @param configuration global scm-manager configuration + * + * @since 1.21 */ public PermissionFilter(ScmConfiguration configuration) { @@ -150,87 +148,76 @@ public abstract class PermissionFilter extends HttpFilter { Subject subject = SecurityUtils.getSubject(); - if (subject.isAuthenticated()) + try { - try + Repository repository = getRepository(request); + + if (repository != null) { - Repository repository = getRepository(request); + boolean writeRequest = isWriteRequest(request); - if (repository != null) + if (hasPermission(repository, writeRequest)) { - boolean writeRequest = isWriteRequest(request); - - if (hasPermission(repository, writeRequest)) + if (logger.isTraceEnabled()) { - if (logger.isTraceEnabled()) - { - logger.trace("{} access to repository {} for user {} granted", - new Object[] { writeRequest - ? "write" - : "read", repository.getName(), subject.getPrincipal() }); - } - - chain.doFilter(request, response); + logger.trace("{} access to repository {} for user {} granted", + new Object[] { writeRequest + ? "write" + : "read", repository.getName(), subject.getPrincipal() }); } - else - { - if (logger.isInfoEnabled()) - { - logger.info("{} access to repository {} for user {} denied", - new Object[] { writeRequest - ? "write" - : "read", repository.getName(), subject.getPrincipal() }); - } - sendAccessDenied(response, subject); - } + chain.doFilter(request, response); } else { - if (logger.isDebugEnabled()) + if (logger.isInfoEnabled()) { - logger.debug("repository not found"); + logger.info("{} access to repository {} for user {} denied", + new Object[] { writeRequest + ? "write" + : "read", repository.getName(), subject.getPrincipal() }); } - response.sendError(HttpServletResponse.SC_NOT_FOUND); + sendAccessDenied(response, subject); } } - catch (ArgumentIsInvalidException ex) + else { - if (logger.isTraceEnabled()) + if (logger.isDebugEnabled()) { - logger.trace( - "wrong request at ".concat(request.getRequestURI()).concat( - " send redirect"), ex); - } - else if (logger.isWarnEnabled()) - { - logger.warn("wrong request at {} send redirect", - request.getRequestURI()); + logger.debug("repository not found"); } - response.sendRedirect(getRepositoryRootHelpUrl(request)); - } - catch (ScmSecurityException ex) - { - if (logger.isWarnEnabled()) - { - logger.warn("user {} has not enough permissions", - subject.getPrincipal()); - } - - sendAccessDenied(response, subject); + response.sendError(HttpServletResponse.SC_NOT_FOUND); } } - else + catch (ArgumentIsInvalidException ex) { - if (logger.isDebugEnabled()) + if (logger.isTraceEnabled()) { - logger.debug("user in not authenticated"); + logger.trace( + "wrong request at ".concat(request.getRequestURI()).concat( + " send redirect"), ex); + } + else if (logger.isWarnEnabled()) + { + logger.warn("wrong request at {} send redirect", + request.getRequestURI()); } - response.sendError(HttpServletResponse.SC_FORBIDDEN); + response.sendRedirect(getRepositoryRootHelpUrl(request)); } + catch (ScmSecurityException ex) + { + if (logger.isWarnEnabled()) + { + logger.warn("user {} has not enough permissions", + subject.getPrincipal()); + } + + sendAccessDenied(response, subject); + } + } /** @@ -269,15 +256,13 @@ public abstract class PermissionFilter extends HttpFilter private void sendAccessDenied(HttpServletResponse response, Subject subject) throws IOException { - - // TODO check anonymous access - if (SCMContext.USER_ANONYMOUS.equals(subject.getPrincipal())) + if (subject.isAuthenticated()) { - HttpUtil.sendUnauthorized(response); + response.sendError(HttpServletResponse.SC_FORBIDDEN); } else { - response.sendError(HttpServletResponse.SC_FORBIDDEN); + HttpUtil.sendUnauthorized(response); } } @@ -322,7 +307,8 @@ public abstract class PermissionFilter extends HttpFilter } else { - permitted = PermissionUtil.hasPermission(repository, PermissionType.READ); + permitted = PermissionUtil.hasPermission(configuration, repository, + PermissionType.READ); } return permitted; 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 08b6932f72..1ddb9f3f0a 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 @@ -49,6 +49,7 @@ import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.SCMContext; import sonia.scm.SCMContextProvider; import sonia.scm.ScmClientConfig; import sonia.scm.ScmState; @@ -61,6 +62,9 @@ import sonia.scm.user.UserManager; //~--- JDK imports ------------------------------------------------------------ +import java.util.Collection; +import java.util.Collections; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -253,7 +257,6 @@ public class AuthenticationResource public Response getState(@Context HttpServletRequest request) { Response response = null; - ScmState state = null; Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) @@ -263,7 +266,16 @@ public class AuthenticationResource logger.debug("return state for user {}", subject.getPrincipal()); } - state = createState(subject); + ScmState state = createState(subject); + + response = Response.ok(state).build(); + } + else if (configuration.isAnonymousAccessEnabled()) + { + User user = new User(SCMContext.USER_ANONYMOUS, "SCM Anonymous", + "scm-anonymous@scm-manager.com"); + ScmState state = createState(user, Collections.EMPTY_LIST); + response = Response.ok(state).build(); } else @@ -292,7 +304,21 @@ public class AuthenticationResource User user = collection.oneByType(User.class); GroupNames groups = collection.oneByType(GroupNames.class); - return new ScmState(contextProvider, user, groups.getCollection(), + return createState(user, groups.getCollection()); + } + + /** + * Method description + * + * + * @param user + * @param groups + * + * @return + */ + private ScmState createState(User user, Collection groups) + { + return new ScmState(contextProvider, user, groups, repositoryManger.getConfiguredTypes(), userManager.getDefaultType(), new ScmClientConfig(configuration)); } diff --git a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java index 657bf134d9..a458138910 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java @@ -35,10 +35,12 @@ package sonia.scm.filter; //~--- non-JDK imports -------------------------------------------------------- +import com.google.inject.Inject; import com.google.inject.Singleton; import org.apache.shiro.subject.Subject; +import sonia.scm.config.ScmConfiguration; import sonia.scm.security.Role; /** @@ -49,6 +51,20 @@ import sonia.scm.security.Role; public class AdminSecurityFilter extends SecurityFilter { + /** + * Constructs ... + * + * + * @param configuration + */ + @Inject + public AdminSecurityFilter(ScmConfiguration configuration) + { + super(configuration); + } + + //~--- get methods ---------------------------------------------------------- + /** * Method description * diff --git a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java index 553356d8f6..971b20335a 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java @@ -35,11 +35,14 @@ package sonia.scm.filter; //~--- non-JDK imports -------------------------------------------------------- +import com.google.inject.Inject; import com.google.inject.Singleton; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; +import sonia.scm.SCMContext; +import sonia.scm.config.ScmConfiguration; import sonia.scm.user.User; import sonia.scm.web.filter.HttpFilter; import sonia.scm.web.filter.SecurityHttpServletRequestWrapper; @@ -64,6 +67,20 @@ public class SecurityFilter extends HttpFilter /** Field description */ public static final String URL_AUTHENTICATION = "/api/rest/authentication"; + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param configuration + */ + @Inject + public SecurityFilter(ScmConfiguration configuration) + { + this.configuration = configuration; + } + //~--- methods -------------------------------------------------------------- /** @@ -92,7 +109,7 @@ public class SecurityFilter extends HttpFilter if (hasPermission(subject)) { chain.doFilter(new SecurityHttpServletRequestWrapper(request, - subject.getPrincipals().oneByType(User.class)), response); + getUser(subject)), response); } else if (subject.isAuthenticated()) { @@ -121,6 +138,37 @@ public class SecurityFilter extends HttpFilter */ protected boolean hasPermission(Subject subject) { - return subject.isAuthenticated(); + return ((configuration != null) + && configuration.isAnonymousAccessEnabled()) || subject.isAuthenticated(); } + + /** + * Method description + * + * + * @param subject + * + * @return + */ + private User getUser(Subject subject) + { + User user = null; + + if (subject.isAuthenticated()) + { + user = subject.getPrincipals().oneByType(User.class); + } + else + { + user = new User(SCMContext.USER_ANONYMOUS, "SCM Anonymous", + "scm-anonymous@scm-manager.com"); + } + + return user; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private ScmConfiguration configuration; } diff --git a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java index 6433b7033b..7fad15c41c 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -54,7 +54,6 @@ import sonia.scm.HandlerEvent; import sonia.scm.SCMContextProvider; import sonia.scm.Type; import sonia.scm.config.ScmConfiguration; -import sonia.scm.security.RepositoryPermission; import sonia.scm.security.ScmSecurityException; import sonia.scm.util.AssertUtil; import sonia.scm.util.CollectionAppender; @@ -869,7 +868,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager { if (!SecurityUtils.getSubject().hasRole("admin")) { - throw new SecurityException("admin role is required"); + throw new ScmSecurityException("admin role is required"); } } @@ -971,8 +970,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager */ private boolean isPermitted(Repository repository, PermissionType type) { - return SecurityUtils.getSubject().isPermitted( - new RepositoryPermission(repository, PermissionType.READ)); + return PermissionUtil.hasPermission(configuration, repository, type); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ApiBasicAuthenticationFilter.java b/scm-webapp/src/main/java/sonia/scm/web/security/ApiBasicAuthenticationFilter.java index b3386a211f..12e168b8ea 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ApiBasicAuthenticationFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ApiBasicAuthenticationFilter.java @@ -35,8 +35,10 @@ package sonia.scm.web.security; //~--- non-JDK imports -------------------------------------------------------- +import com.google.inject.Inject; import com.google.inject.Singleton; +import sonia.scm.config.ScmConfiguration; import sonia.scm.web.filter.BasicAuthenticationFilter; //~--- JDK imports ------------------------------------------------------------ @@ -65,6 +67,20 @@ public class ApiBasicAuthenticationFilter extends BasicAuthenticationFilter /** Field description */ public static final String URI_STATE = "/api/rest/authentication/state"; + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param configuration + */ + @Inject + public ApiBasicAuthenticationFilter(ScmConfiguration configuration) + { + super(configuration); + } + //~--- methods -------------------------------------------------------------- /**