diff --git a/pom.xml b/pom.xml index 6c99790d77..6496035d47 100644 --- a/pom.xml +++ b/pom.xml @@ -401,6 +401,9 @@ 2.3.19 7.6.7.v20120910 + + 1.2.1 + 2.1.0.201209190230-r 1.7.5-2 diff --git a/scm-clients/pom.xml b/scm-clients/pom.xml index a58a221f2e..f8c84d7cac 100644 --- a/scm-clients/pom.xml +++ b/scm-clients/pom.xml @@ -23,10 +23,50 @@ + + - sonia.scm scm-core + sonia.scm + jar 1.21-SNAPSHOT + + + shiro-core + org.apache.shiro + + + aopalliance + aopalliance + + + guice + com.google.inject + + + guice-multibindings + com.google.inject.extensions + + + guice-servlet + com.google.inject.extensions + + + jersey-core + com.sun.jersey + + + guice-throwingproviders + com.google.inject.extensions + + + commons-lang + commons-lang + + diff --git a/scm-clients/scm-cli-client/pom.xml b/scm-clients/scm-cli-client/pom.xml index bf5c6aed48..f46b1256c6 100644 --- a/scm-clients/scm-cli-client/pom.xml +++ b/scm-clients/scm-cli-client/pom.xml @@ -32,26 +32,6 @@ provided - - scm-core - sonia.scm - 1.21-SNAPSHOT - - - guice - com.google.inject - - - guice-servlet - com.google.inject.extensions - - - guice-multibindings - com.google.inject.extensions - - - - sonia.scm.clients scm-client-impl diff --git a/scm-clients/scm-client-api/pom.xml b/scm-clients/scm-client-api/pom.xml index 54cb43b037..0ef7e09a76 100644 --- a/scm-clients/scm-client-api/pom.xml +++ b/scm-clients/scm-client-api/pom.xml @@ -32,7 +32,7 @@ 1.1 provided - + \ No newline at end of file diff --git a/scm-clients/scm-client-impl/pom.xml b/scm-clients/scm-client-impl/pom.xml index 11790b70bf..5b5dd2d5b7 100644 --- a/scm-clients/scm-client-impl/pom.xml +++ b/scm-clients/scm-client-impl/pom.xml @@ -33,30 +33,6 @@ provided - - scm-core - sonia.scm - 1.21-SNAPSHOT - - - aopalliance - aopalliance - - - guice - com.google.inject - - - guice-multibindings - com.google.inject.extensions - - - guice-servlet - com.google.inject.extensions - - - - sonia.scm.clients scm-client-api diff --git a/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/ClientUtil.java b/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/ClientUtil.java index 7a19c5d916..e01307fed7 100644 --- a/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/ClientUtil.java +++ b/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/ClientUtil.java @@ -66,7 +66,7 @@ public class ClientUtil * @param response */ public static void appendContent(ScmClientException exception, - ClientResponse response) + ClientResponse response) { try { @@ -86,12 +86,18 @@ public class ClientUtil * @param expectedStatusCode */ public static void checkResponse(ClientResponse response, - int expectedStatusCode) + int expectedStatusCode) { int sc = response.getStatus(); if (sc != expectedStatusCode) { + if (logger.isWarnEnabled()) + { + logger.warn("response code {} expected, but {} returned", + expectedStatusCode, sc); + } + sendException(response, sc); } } @@ -109,6 +115,11 @@ public class ClientUtil if (sc >= 300) { + if (logger.isWarnEnabled()) + { + logger.warn("request failed, response code {} returned", sc); + } + sendException(response, sc); } } @@ -152,7 +163,7 @@ public class ClientUtil * @return */ public static WebResource createResource(Client client, String url, - boolean enableLogging) + boolean enableLogging) { WebResource resource = client.resource(url); diff --git a/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyRepositoryClientHandler.java b/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyRepositoryClientHandler.java index cc93849fa3..f1c204531b 100644 --- a/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyRepositoryClientHandler.java +++ b/scm-clients/scm-client-impl/src/main/java/sonia/scm/client/JerseyRepositoryClientHandler.java @@ -37,7 +37,6 @@ package sonia.scm.client; import sonia.scm.NotSupportedFeatuerException; import sonia.scm.Type; -import sonia.scm.repository.Changeset; import sonia.scm.repository.Repository; import sonia.scm.repository.Tags; diff --git a/scm-clients/scm-client-impl/src/test/java/sonia/scm/client/it/ClientTestUtil.java b/scm-clients/scm-client-impl/src/test/java/sonia/scm/client/it/ClientTestUtil.java index b2efca3439..a9caf9d50e 100644 --- a/scm-clients/scm-client-impl/src/test/java/sonia/scm/client/it/ClientTestUtil.java +++ b/scm-clients/scm-client-impl/src/test/java/sonia/scm/client/it/ClientTestUtil.java @@ -38,7 +38,6 @@ package sonia.scm.client.it; import sonia.scm.client.ClientUtil; import sonia.scm.client.JerseyClientProvider; import sonia.scm.client.JerseyClientSession; -import sonia.scm.client.ScmUrlProvider; import sonia.scm.config.ScmConfiguration; import sonia.scm.url.UrlProvider; @@ -106,7 +105,7 @@ public class ClientTestUtil * */ public static JerseyClientSession createSession(String username, - String password) + String password) { JerseyClientProvider provider = new JerseyClientProvider(REQUEST_LOGGING); diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 5147be4351..badb2baa00 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -33,6 +33,14 @@ ${slf4j.version} + + + + org.apache.shiro + shiro-core + ${shiro.version} + + diff --git a/scm-core/src/main/java/sonia/scm/HandlerEvent.java b/scm-core/src/main/java/sonia/scm/HandlerEvent.java index 0bedf4be5b..a15522be33 100644 --- a/scm-core/src/main/java/sonia/scm/HandlerEvent.java +++ b/scm-core/src/main/java/sonia/scm/HandlerEvent.java @@ -44,33 +44,75 @@ public enum HandlerEvent /** * After a new object is stored by a handler. */ - CREATE, + CREATE(true), /** * After a object is modified by a handler. */ - MODIFY, + MODIFY(true), /** * After a object is removed by a handler. */ - DELETE, + DELETE(true), /** * Before a new object is stored by a handler. * @since 1.16 */ - BEFORE_CREATE, + BEFORE_CREATE(false), /** * Before a object is modified by a handler. * @since 1.16 */ - BEFORE_MODIFY, + BEFORE_MODIFY(false), /** * Before a object is removed by a handler. * @since 1.16 */ - BEFORE_DELETE + BEFORE_DELETE(false); + + /** + * Constructs ... + * + * + * @param post + */ + private HandlerEvent(boolean post) + { + this.post = post; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Returns true if the event is fired after the action is occurred. + * + * + * @return true if the event is fired after the action is occurred + * @since 1.21 + */ + public boolean isPost() + { + return post; + } + + /** + * Returns true if the event is fired before the action is occurred. + * + * + * @return true if the event is fired before the action is occurred + * @since 1.21 + */ + public boolean isPre() + { + return !post; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private boolean post; } diff --git a/scm-core/src/main/java/sonia/scm/SCMContext.java b/scm-core/src/main/java/sonia/scm/SCMContext.java index b376646de0..be14775fc3 100644 --- a/scm-core/src/main/java/sonia/scm/SCMContext.java +++ b/scm-core/src/main/java/sonia/scm/SCMContext.java @@ -35,6 +35,7 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import sonia.scm.user.User; import sonia.scm.util.ServiceUtil; /** @@ -52,6 +53,14 @@ public class SCMContext /** Name of the anonymous user */ public static final String USER_ANONYMOUS = "anonymous"; + /** + * the anonymous user + * @since 1.21 + */ + public static final User ANONYMOUS = new User(USER_ANONYMOUS, + "SCM Anonymous", + "scm-anonymous@scm-manager.com"); + /** Singleton instance of {@link SCMContextProvider} */ private static volatile SCMContextProvider provider; diff --git a/scm-core/src/main/java/sonia/scm/ScmState.java b/scm-core/src/main/java/sonia/scm/ScmState.java index 3de189d7cf..8508d90c67 100644 --- a/scm-core/src/main/java/sonia/scm/ScmState.java +++ b/scm-core/src/main/java/sonia/scm/ScmState.java @@ -73,10 +73,10 @@ public class ScmState * @param repositoryTypes - available repository types * @param clientConfig - client configuration */ + @Deprecated public ScmState(SCMContextProvider provider, - WebSecurityContext securityContext, - Collection repositoryTypes, - ScmClientConfig clientConfig) + WebSecurityContext securityContext, Collection repositoryTypes, + ScmClientConfig clientConfig) { this(provider, securityContext, repositoryTypes, null, clientConfig); } @@ -93,10 +93,10 @@ public class ScmState * * @since 1.14 */ + @Deprecated public ScmState(SCMContextProvider provider, - WebSecurityContext securityContext, - Collection repositoryTypes, String defaultUserType, - ScmClientConfig clientConfig) + WebSecurityContext securityContext, Collection repositoryTypes, + String defaultUserType, ScmClientConfig clientConfig) { this.version = provider.getVersion(); this.user = securityContext.getUser(); @@ -106,6 +106,31 @@ public class ScmState this.defaultUserType = defaultUserType; } + /** + * Constructs {@link ScmState} object. + * + * + * @param provider context provider + * @param user current user + * @param groups groups of the current user + * @param repositoryTypes available repository types + * @param defaultUserType default user type + * @param clientConfig client configuration + * + * @since 1.21 + */ + public ScmState(SCMContextProvider provider, User user, + Collection groups, Collection repositoryTypes, + String defaultUserType, ScmClientConfig clientConfig) + { + this.version = provider.getVersion(); + this.user = user; + this.groups = groups; + this.repositoryTypes = repositoryTypes; + this.clientConfig = clientConfig; + this.defaultUserType = defaultUserType; + } + //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationSecurityContext.java b/scm-core/src/main/java/sonia/scm/group/GroupNames.java similarity index 67% rename from scm-webapp/src/main/java/sonia/scm/web/security/AdministrationSecurityContext.java rename to scm-core/src/main/java/sonia/scm/group/GroupNames.java index a77142d3af..867e575e64 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/AdministrationSecurityContext.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupNames.java @@ -30,38 +30,64 @@ */ - -package sonia.scm.web.security; +package sonia.scm.group; //~--- non-JDK imports -------------------------------------------------------- -import sonia.scm.user.User; +import com.google.common.collect.Lists; //~--- JDK imports ------------------------------------------------------------ -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.io.Serializable; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; /** + * This class represents all associated groups for a user. * * @author Sebastian Sdorra + * @since 1.21 */ -public class AdministrationSecurityContext implements WebSecurityContext +public final class GroupNames implements Serializable, Iterable { + /** Field description */ + private static final long serialVersionUID = 8615685985213897947L; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + */ + public GroupNames() + { + this.collection = Collections.EMPTY_LIST; + } + /** * Constructs ... * * - * @param user + * @param collection */ - public AdministrationSecurityContext(User user) + public GroupNames(Collection collection) { - this.user = user; + this.collection = Collections.unmodifiableCollection(collection); + } + + /** + * Constructs ... + * + * + * @param groupName + * @param groupNames + */ + public GroupNames(String groupName, String... groupNames) + { + this.collection = Lists.asList(groupName, groupNames); } //~--- methods -------------------------------------------------------------- @@ -70,32 +96,25 @@ public class AdministrationSecurityContext implements WebSecurityContext * Method description * * - * @param request - * @param response - * @param username - * @param password + * @param groupName * * @return */ - @Override - public User authenticate(HttpServletRequest request, - HttpServletResponse response, String username, - String password) + public boolean contains(String groupName) { - throw new UnsupportedOperationException("Not supported yet."); + return collection.contains(groupName); } /** * Method description * * - * @param request - * @param response + * @return */ @Override - public void logout(HttpServletRequest request, HttpServletResponse response) + public Iterator iterator() { - throw new UnsupportedOperationException("Not supported yet."); + return collection.iterator(); } //~--- get methods ---------------------------------------------------------- @@ -106,41 +125,13 @@ public class AdministrationSecurityContext implements WebSecurityContext * * @return */ - @Override - public Collection getGroups() + public Collection getCollection() { - return groups; - } - - /** - * Method description - * - * - * @return - */ - @Override - public User getUser() - { - return user; - } - - /** - * Method description - * - * - * @return - */ - @Override - public boolean isAuthenticated() - { - return true; + return collection; } //~--- fields --------------------------------------------------------------- /** Field description */ - private List groups = new ArrayList(); - - /** Field description */ - private User user; + private Collection collection; } 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 dc6a48b118..6d77ed8617 100644 --- a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java @@ -37,18 +37,21 @@ package sonia.scm.repository; import com.google.inject.Provider; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.config.ScmConfiguration; +import sonia.scm.group.GroupNames; +import sonia.scm.security.Role; import sonia.scm.security.ScmSecurityException; -import sonia.scm.user.User; import sonia.scm.util.AssertUtil; import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ -import java.util.Collection; import java.util.List; /** @@ -73,14 +76,13 @@ public class PermissionUtil * @param repository * @param securityContext * @param pt + * @deprecated */ + @Deprecated public static void assertPermission(Repository repository, - WebSecurityContext securityContext, PermissionType pt) + WebSecurityContext securityContext, PermissionType pt) { - if (!hasPermission(repository, securityContext, pt)) - { - throw new ScmSecurityException("action denied"); - } + assertPermission(repository, pt); } /** @@ -91,13 +93,32 @@ public class PermissionUtil * @param securityContextProvider * @param pt */ + @Deprecated public static void assertPermission(Repository repository, - Provider securityContextProvider, - PermissionType pt) + Provider securityContextProvider, PermissionType pt) { assertPermission(repository, securityContextProvider.get(), pt); } + /** + * Method description + * + * + * @param repository + * @param securityContextProvider + * @param pt + * + * @since 1.21 + */ + @Deprecated + public static void assertPermission(Repository repository, PermissionType pt) + { + if (!hasPermission(null, repository, pt)) + { + throw new ScmSecurityException("action denied"); + } + } + //~--- get methods ---------------------------------------------------------- /** @@ -109,12 +130,13 @@ public class PermissionUtil * @param pt * * @return + * @deprecated */ + @Deprecated public static boolean hasPermission(Repository repository, - Provider securityContextProvider, - PermissionType pt) + Provider securityContextProvider, PermissionType pt) { - return hasPermission(repository, securityContextProvider.get(), pt); + return hasPermission(null, repository, pt); } /** @@ -126,39 +148,67 @@ public class PermissionUtil * @param pt * * @return + * @deprecated use {@link #hasPermission(Repository,PermissionType)} instead */ + @Deprecated public static boolean hasPermission(Repository repository, - WebSecurityContext securityContext, PermissionType pt) + WebSecurityContext securityContext, PermissionType pt) + { + return hasPermission(null, repository, pt); + } + + /** + * Method description + * + * + * @param configuration + * @param repository + * @param pt + * + * @return + * + * @since 1.21 + */ + public static boolean hasPermission(ScmConfiguration configuration, + Repository repository, PermissionType pt) { boolean result = false; - if (securityContext != null) + Subject subject = SecurityUtils.getSubject(); + + if (subject.isAuthenticated()) { - User user = securityContext.getUser(); + String username = subject.getPrincipal().toString(); - if (user != null) + AssertUtil.assertIsNotEmpty(username); + + if (subject.hasRole(Role.ADMIN) + || ((pt == PermissionType.READ) && repository.isPublicReadable())) { - String username = user.getName(); + result = true; + } + else + { + List permissions = repository.getPermissions(); - AssertUtil.assertIsNotEmpty(username); - - if (user.isAdmin() - || ((pt == PermissionType.READ) && repository.isPublicReadable())) + if (permissions != null) { - result = true; - } - else - { - List permissions = repository.getPermissions(); + GroupNames groupNames = + subject.getPrincipals().oneByType(GroupNames.class); + + result = hasPermission(permissions, username, groupNames, pt); - if (permissions != null) - { - result = hasPermission(permissions, username, - securityContext.getGroups(), pt); - } } } } + else + { + + // check anonymous access + result = (configuration != null) + && configuration.isAnonymousAccessEnabled() + && repository.isPublicReadable() && (pt == PermissionType.READ); + } return result; } @@ -173,10 +223,28 @@ public class PermissionUtil * * @return true if the repository is writable * @since 1.14 + * @deprecated use {@link #isWritable(ScmConfiguration, Repository)} instead + */ + @Deprecated + public static boolean isWritable(ScmConfiguration configuration, + Repository repository, WebSecurityContext securityContext) + { + return isWritable(configuration, repository); + } + + /** + * Returns true if the repository is writable. + * + * + * @param configuration SCM-Manager main configuration + * @param repository repository to check + * @param securityContext current user security context + * + * @return true if the repository is writable + * @since 1.21 */ public static boolean isWritable(ScmConfiguration configuration, - Repository repository, - WebSecurityContext securityContext) + Repository repository) { boolean permitted = false; @@ -185,13 +253,13 @@ public class PermissionUtil if (logger.isWarnEnabled()) { logger.warn("{} is archived and is not writeable", - repository.getName()); + repository.getName()); } } else { - permitted = PermissionUtil.hasPermission(repository, securityContext, - PermissionType.WRITE); + permitted = PermissionUtil.hasPermission(configuration, repository, + PermissionType.WRITE); } return permitted; @@ -209,7 +277,7 @@ public class PermissionUtil * @return */ private static boolean hasPermission(List permissions, - String username, Collection groups, PermissionType pt) + String username, GroupNames groups, PermissionType pt) { boolean result = false; @@ -218,8 +286,8 @@ public class PermissionUtil String name = p.getName(); if (((name != null) && (p.getType().getValue() >= pt.getValue())) - && (name.equals(username) - || (p.isGroupPermission() && groups.contains(p.getName())))) + && (name.equals(username) + || (p.isGroupPermission() && groups.contains(p.getName())))) { result = true; 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 23825d0ee5..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 @@ -38,7 +38,6 @@ package sonia.scm.repository.api; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; import org.slf4j.Logger; @@ -47,6 +46,7 @@ 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; @@ -65,7 +65,6 @@ import sonia.scm.repository.Tags; import sonia.scm.repository.spi.RepositoryServiceProvider; import sonia.scm.repository.spi.RepositoryServiceResolver; import sonia.scm.security.ScmSecurityException; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -134,16 +133,40 @@ 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, - Provider securityContextProvider, 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.securityContextProvider = securityContextProvider; this.resolvers = resolvers; this.preProcessorUtil = preProcessorUtil; @@ -250,8 +273,11 @@ public final class RepositoryServiceFactory Preconditions.checkNotNull(repository, "repository is required"); // check for read permissions of current user - PermissionUtil.assertPermission(repository, securityContextProvider, - PermissionType.READ); + if (!PermissionUtil.hasPermission(configuration, repository, + PermissionType.READ)) + { + throw new ScmSecurityException("read permission are required"); + } RepositoryService service = null; @@ -404,6 +430,9 @@ public final class RepositoryServiceFactory /** Field description */ private CacheManager cacheManager; + /** scm-manager configuration */ + private ScmConfiguration configuration; + /** Field description */ private PreProcessorUtil preProcessorUtil; @@ -412,7 +441,4 @@ public final class RepositoryServiceFactory /** Field description */ private Set resolvers; - - /** Field description */ - private Provider securityContextProvider; } diff --git a/scm-core/src/main/java/sonia/scm/security/RepositoryPermission.java b/scm-core/src/main/java/sonia/scm/security/RepositoryPermission.java new file mode 100644 index 0000000000..b1ffcde938 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/RepositoryPermission.java @@ -0,0 +1,207 @@ +/** + * 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.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Objects; + +import org.apache.shiro.authz.Permission; + +import sonia.scm.repository.PermissionType; +import sonia.scm.repository.Repository; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.Serializable; + +/** + * This class represents the permission to a repository of a user. + * + * @author Sebastian Sdorra + * @since 1.21 + */ +public final class RepositoryPermission implements Permission, Serializable +{ + + /** Field description */ + public static final String WILDCARD = "*"; + + /** Field description */ + private static final long serialVersionUID = 3832804235417228043L; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param repository + * @param permissionType + */ + public RepositoryPermission(Repository repository, + PermissionType permissionType) + { + this(repository.getId(), permissionType); + } + + /** + * Constructs ... + * + * + * @param repositoryId + * @param permissionType + */ + public RepositoryPermission(String repositoryId, + PermissionType permissionType) + { + this.repositoryId = repositoryId; + this.permissionType = permissionType; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param obj + * + * @return + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final RepositoryPermission other = (RepositoryPermission) obj; + + return Objects.equal(repositoryId, other.repositoryId) + && Objects.equal(permissionType, other.permissionType); + } + + /** + * Method description + * + * + * @return + */ + @Override + public int hashCode() + { + return Objects.hashCode(repositoryId, permissionType); + } + + /** + * Method description + * + * + * @param p + * + * @return + */ + @Override + public boolean implies(Permission p) + { + boolean result = false; + + if (p instanceof RepositoryPermission) + { + RepositoryPermission rp = (RepositoryPermission) p; + + //J- + result = (repositoryId.equals(WILDCARD) || repositoryId.equals(rp.repositoryId)) + && (permissionType.getValue() >= rp.permissionType.getValue()); + //J+ + } + + return result; + } + + /** + * Method description + * + * + * @return + */ + @Override + public String toString() + { + //J- + return Objects.toStringHelper(this) + .add("repositoryId", repositoryId) + .add("permissionType", permissionType) + .toString(); + //J+ + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public PermissionType getPermissionType() + { + return permissionType; + } + + /** + * Method description + * + * + * @return + */ + public String getRepositoryId() + { + return repositoryId; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private PermissionType permissionType; + + /** Field description */ + private String repositoryId; +} diff --git a/scm-core/src/main/java/sonia/scm/security/Role.java b/scm-core/src/main/java/sonia/scm/security/Role.java new file mode 100644 index 0000000000..b82901df34 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/Role.java @@ -0,0 +1,48 @@ +/** + * 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.security; + +/** + * + * @author Sebastian Sdorra + * @since 1.21 + */ +public final class Role +{ + + /** Field description */ + public static final String ADMIN = "admin"; + + /** Field description */ + public static final String USER = "user"; +} diff --git a/scm-core/src/main/java/sonia/scm/security/SecurityContext.java b/scm-core/src/main/java/sonia/scm/security/SecurityContext.java index 226a247d4a..34baca2f5e 100644 --- a/scm-core/src/main/java/sonia/scm/security/SecurityContext.java +++ b/scm-core/src/main/java/sonia/scm/security/SecurityContext.java @@ -40,7 +40,9 @@ import sonia.scm.user.User; /** * * @author Sebastian Sdorra + * @deprecated use {@link SecurityUtils#getSecurityManager()} instead. */ +@Deprecated public interface SecurityContext { diff --git a/scm-core/src/main/java/sonia/scm/security/Tokens.java b/scm-core/src/main/java/sonia/scm/security/Tokens.java new file mode 100644 index 0000000000..d9f668fa12 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/Tokens.java @@ -0,0 +1,70 @@ +/** + * 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.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.Subject; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; + +/** + * Create tokens for security reasons. + * + * @author Sebastian Sdorra + * @since 1.21 + */ +public final class Tokens +{ + + /** + * Build an {@link AuthenticationToken} for use with + * {@link Subject#login(org.apache.shiro.authc.AuthenticationToken)}. + * + * + * @param request servlet request + * @param username username of the user to authenticate + * @param password password of the user to authenticate + * + * @return + */ + public static AuthenticationToken createAuthenticationToken( + HttpServletRequest request, String username, String password) + { + return new UsernamePasswordToken(username, password, + request.getRemoteAddr()); + } +} diff --git a/scm-core/src/main/java/sonia/scm/template/TemplateParseException.java b/scm-core/src/main/java/sonia/scm/template/TemplateParseException.java index f374b43b27..2de045aeef 100644 --- a/scm-core/src/main/java/sonia/scm/template/TemplateParseException.java +++ b/scm-core/src/main/java/sonia/scm/template/TemplateParseException.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-core/src/main/java/sonia/scm/util/SecurityUtil.java b/scm-core/src/main/java/sonia/scm/util/SecurityUtil.java index 1bf585ddf9..d8bf4c40d7 100644 --- a/scm-core/src/main/java/sonia/scm/util/SecurityUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/SecurityUtil.java @@ -37,7 +37,11 @@ package sonia.scm.util; import com.google.inject.Provider; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + import sonia.scm.SCMContext; +import sonia.scm.security.Role; import sonia.scm.security.ScmSecurityException; import sonia.scm.security.SecurityContext; import sonia.scm.user.User; @@ -54,36 +58,51 @@ public class SecurityUtil * * * @param contextProvider + * @deprecated use {@link Subject#checkRole(java.lang.String)} with { + * @link Role#ADMIN} instead. */ + @Deprecated public static void assertIsAdmin( - Provider contextProvider) + Provider contextProvider) { - assertIsAdmin(contextProvider.get()); + assertIsAdmin(); } /** - * Method description + * This method is only present for compatibility reasons. + * Use {@link Subject#checkRole(java.lang.String)} with { + * @link Role#ADMIN} instead. * - * - * @param context + * @since 1.21 */ - public static void assertIsAdmin(SecurityContext context) + public static void assertIsAdmin() { - AssertUtil.assertIsNotNull(context); + Subject subject = SecurityUtils.getSubject(); - User user = context.getUser(); - - if (user == null) + if (!subject.isAuthenticated()) { throw new ScmSecurityException("user is not authenticated"); } - - if (!user.isAdmin()) + else if (!subject.hasRole(Role.ADMIN)) { throw new ScmSecurityException("admin account is required"); } } + /** + * Method description + * + * + * @param context + * @deprecated use {@link Subject#checkRole(java.lang.String)} with { + * @link Role#ADMIN} instead. + */ + @Deprecated + public static void assertIsAdmin(SecurityContext context) + { + assertIsAdmin(); + } + /** * Method description * @@ -91,7 +110,7 @@ public class SecurityUtil * @param contextProvider */ public static void assertIsNotAnonymous( - Provider contextProvider) + Provider contextProvider) { if (isAnonymous(contextProvider)) { @@ -124,7 +143,7 @@ public class SecurityUtil * @return */ public static User getCurrentUser( - Provider contextProvider) + Provider contextProvider) { AssertUtil.assertIsNotNull(contextProvider); @@ -151,7 +170,7 @@ public class SecurityUtil * @return */ public static boolean isAdmin( - Provider contextProvider) + Provider contextProvider) { return isAdmin(contextProvider.get()); } @@ -169,7 +188,7 @@ public class SecurityUtil AssertUtil.assertIsNotNull(contextProvider); return (contextProvider.getUser() != null) - && contextProvider.getUser().isAdmin(); + && contextProvider.getUser().isAdmin(); } /** @@ -181,7 +200,7 @@ public class SecurityUtil * @return */ public static boolean isAnonymous( - Provider contextProvider) + Provider contextProvider) { return isAnonymous(contextProvider.get()); } 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 af72d763e4..fc2cc6d681 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 @@ -39,11 +39,17 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; +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.user.User; -import sonia.scm.util.AssertUtil; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; import sonia.scm.web.security.WebSecurityContext; @@ -87,12 +93,23 @@ public class BasicAuthenticationFilter extends HttpFilter * * * @param securityContextProvider + * @deprecated use the constructor with out arguments instead. + */ + @Deprecated + public BasicAuthenticationFilter( + Provider securityContextProvider) {} + + /** + * Constructs a new basic authenticaton filter + * + * @param configuration scm-manager global configuration + * + * @since 1.21 */ @Inject - public BasicAuthenticationFilter( - Provider securityContextProvider) + public BasicAuthenticationFilter(ScmConfiguration configuration) { - this.securityContextProvider = securityContextProvider; + this.configuration = configuration; } //~--- methods -------------------------------------------------------------- @@ -110,12 +127,10 @@ public class BasicAuthenticationFilter extends HttpFilter */ @Override protected void doFilter(HttpServletRequest request, - HttpServletResponse response, FilterChain chain) - throws IOException, ServletException + HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { - WebSecurityContext securityContext = securityContextProvider.get(); - - AssertUtil.assertIsNotNull(securityContext); + Subject subject = SecurityUtils.getSubject(); User user = null; String authentication = request.getHeader(HEADER_AUTHORIZATION); @@ -127,7 +142,7 @@ public class BasicAuthenticationFilter extends HttpFilter logger.trace("found basic authorization header, start authentication"); } - user = authenticate(request, response, securityContext, authentication); + user = authenticate(request, response, subject, authentication); if (logger.isTraceEnabled()) { @@ -141,14 +156,20 @@ public class BasicAuthenticationFilter extends HttpFilter } } } - else if (securityContext.isAuthenticated()) + else if (subject.isAuthenticated()) { if (logger.isTraceEnabled()) { logger.trace("user is allready authenticated"); } - user = securityContext.getUser(); + user = subject.getPrincipals().oneByType(User.class); + } + else if ((configuration != null) + && configuration.isAnonymousAccessEnabled()) + { + user = SCMContext.ANONYMOUS; + } if (user == null) @@ -158,12 +179,12 @@ public class BasicAuthenticationFilter extends HttpFilter logger.trace("could not find user send unauthorized"); } - HttpUtil.sendUnauthorized(request, response); + handleUnauthorized(request, response, chain); } else { chain.doFilter(new SecurityHttpServletRequestWrapper(request, user), - response); + response); } } @@ -181,9 +202,8 @@ public class BasicAuthenticationFilter extends HttpFilter * @since 1.8 */ protected void handleUnauthorized(HttpServletRequest request, - HttpServletResponse response, - FilterChain chain) - throws IOException, ServletException + HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { HttpUtil.sendUnauthorized(request, response); } @@ -195,14 +215,13 @@ public class BasicAuthenticationFilter extends HttpFilter * @param request * @param response * @param securityContext + * @param subject * @param authentication * * @return */ private User authenticate(HttpServletRequest request, - HttpServletResponse response, - WebSecurityContext securityContext, - String authentication) + HttpServletResponse response, Subject subject, String authentication) { String token = authentication.substring(6); @@ -223,8 +242,25 @@ public class BasicAuthenticationFilter extends HttpFilter logger.trace("try to authenticate user {}", username); } - user = securityContext.authenticate(request, response, username, - password); + try + { + + subject.login(new UsernamePasswordToken(username, password, + request.getRemoteAddr())); + user = subject.getPrincipals().oneByType(User.class); + } + catch (AuthenticationException ex) + { + if (logger.isTraceEnabled()) + { + logger.trace("authentication failed for user ".concat(username), + ex); + } + else if (logger.isWarnEnabled()) + { + logger.warn("authentication failed for user {}", username); + } + } } else if (logger.isWarnEnabled()) { @@ -242,5 +278,5 @@ public class BasicAuthenticationFilter extends HttpFilter //~--- fields --------------------------------------------------------------- /** Field description */ - private Provider securityContextProvider; + 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 fd03813a46..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 @@ -38,18 +38,18 @@ package sonia.scm.web.filter; import com.google.common.base.Splitter; import com.google.inject.Provider; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + 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; import sonia.scm.repository.Repository; import sonia.scm.security.ScmSecurityException; -import sonia.scm.user.User; -import sonia.scm.util.AssertUtil; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; import sonia.scm.web.security.WebSecurityContext; @@ -78,6 +78,18 @@ public abstract class PermissionFilter extends HttpFilter //~--- constructors --------------------------------------------------------- + /** + * Constructs a new permission filter + * + * @param configuration global scm-manager configuration + * + * @since 1.21 + */ + public PermissionFilter(ScmConfiguration configuration) + { + this.configuration = configuration; + } + /** * Constructs ... * @@ -85,12 +97,13 @@ public abstract class PermissionFilter extends HttpFilter * * @param configuration * @param securityContextProvider + * @deprecated */ + @Deprecated public PermissionFilter(ScmConfiguration configuration, - Provider securityContextProvider) + Provider securityContextProvider) { this.configuration = configuration; - this.securityContextProvider = securityContextProvider; } //~--- get methods ---------------------------------------------------------- @@ -130,97 +143,81 @@ public abstract class PermissionFilter extends HttpFilter */ @Override protected void doFilter(HttpServletRequest request, - HttpServletResponse response, FilterChain chain) - throws IOException, ServletException + HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { - WebSecurityContext securityContext = securityContextProvider.get(); + Subject subject = SecurityUtils.getSubject(); - AssertUtil.assertIsNotNull(securityContext); - - User user = securityContext.getUser(); - - if (user != null) + 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, securityContext, writeRequest)) + if (logger.isTraceEnabled()) { - if (logger.isTraceEnabled()) - { - logger.trace("{} access to repository {} for user {} granted", - new Object[] { writeRequest - ? "write" - : "read", repository.getName(), - user.getName() }); - } - - 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(), - user.getName() }); - } - sendAccessDenied(response, user); - } + 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", user.getName()); - } - - sendAccessDenied(response, user); + 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); + } + } /** @@ -234,8 +231,8 @@ public abstract class PermissionFilter extends HttpFilter private String extractType(HttpServletRequest request) { Iterator it = Splitter.on( - HttpUtil.SEPARATOR_PATH).omitEmptyStrings().split( - request.getRequestURI()).iterator(); + HttpUtil.SEPARATOR_PATH).omitEmptyStrings().split( + request.getRequestURI()).iterator(); String type = it.next(); if (Util.isNotEmpty(request.getContextPath())) @@ -252,19 +249,20 @@ public abstract class PermissionFilter extends HttpFilter * * @param response * @param user + * @param subject * * @throws IOException */ - private void sendAccessDenied(HttpServletResponse response, User user) - throws IOException + private void sendAccessDenied(HttpServletResponse response, Subject subject) + throws IOException { - if (SCMContext.USER_ANONYMOUS.equals(user.getName())) + if (subject.isAuthenticated()) { - HttpUtil.sendUnauthorized(response); + response.sendError(HttpServletResponse.SC_FORBIDDEN); } else { - response.sendError(HttpServletResponse.SC_FORBIDDEN); + HttpUtil.sendUnauthorized(response); } } @@ -299,21 +297,18 @@ public abstract class PermissionFilter extends HttpFilter * * @return */ - private boolean hasPermission(Repository repository, - WebSecurityContext securityContext, - boolean writeRequest) + private boolean hasPermission(Repository repository, boolean writeRequest) { boolean permitted = false; if (writeRequest) { - permitted = PermissionUtil.isWritable(configuration, repository, - securityContext); + permitted = PermissionUtil.isWritable(configuration, repository); } else { - permitted = PermissionUtil.hasPermission(repository, securityContext, - PermissionType.READ); + permitted = PermissionUtil.hasPermission(configuration, repository, + PermissionType.READ); } return permitted; @@ -321,9 +316,6 @@ public abstract class PermissionFilter extends HttpFilter //~--- fields --------------------------------------------------------------- - /** Field description */ - protected Provider securityContextProvider; - /** Field description */ private ScmConfiguration configuration; } diff --git a/scm-core/src/main/java/sonia/scm/web/filter/ProviderPermissionFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/ProviderPermissionFilter.java index 6d9abe73b1..d72b4a68b2 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/ProviderPermissionFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/ProviderPermissionFilter.java @@ -67,6 +67,21 @@ public abstract class ProviderPermissionFilter extends PermissionFilter //~--- constructors --------------------------------------------------------- + /** + * Constructs ... + * + * + * @param configuration + * @param repositoryProvider + * @since 1.21 + */ + public ProviderPermissionFilter(ScmConfiguration configuration, + RepositoryProvider repositoryProvider) + { + super(configuration); + this.repositoryProvider = repositoryProvider; + } + /** * Constructs ... * @@ -75,14 +90,14 @@ public abstract class ProviderPermissionFilter extends PermissionFilter * @param configuration * @param securityContextProvider * @param repositoryProvider + * @deprecated */ - public ProviderPermissionFilter( - ScmConfiguration configuration, - Provider securityContextProvider, - RepositoryProvider repositoryProvider) + @Deprecated + public ProviderPermissionFilter(ScmConfiguration configuration, + Provider securityContextProvider, + RepositoryProvider repositoryProvider) { - super(configuration, securityContextProvider); - this.repositoryProvider = repositoryProvider; + this(configuration, repositoryProvider); } //~--- get methods ---------------------------------------------------------- @@ -107,7 +122,7 @@ public abstract class ProviderPermissionFilter extends PermissionFilter catch (ProvisionException ex) { Throwables.propagateIfInstanceOf(ex.getCause(), - IllegalStateException.class); + IllegalStateException.class); if (logger.isErrorEnabled()) { diff --git a/scm-core/src/main/java/sonia/scm/web/filter/RegexPermissionFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/RegexPermissionFilter.java index 0deac57121..a064d12115 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/RegexPermissionFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/RegexPermissionFilter.java @@ -71,15 +71,31 @@ public abstract class RegexPermissionFilter extends PermissionFilter * @param securityContextProvider * @param repositoryManager */ - public RegexPermissionFilter( - ScmConfiguration configuration, - Provider securityContextProvider, - RepositoryManager repositoryManager) + public RegexPermissionFilter(ScmConfiguration configuration, + RepositoryManager repositoryManager) { - super(configuration, securityContextProvider); + super(configuration); this.repositoryManager = repositoryManager; } + /** + * Constructs ... + * + * + * + * @param configuration + * @param securityContextProvider + * @param repositoryManager + * @deprecated + */ + @Deprecated + public RegexPermissionFilter(ScmConfiguration configuration, + Provider securityContextProvider, + RepositoryManager repositoryManager) + { + this(configuration, repositoryManager); + } + //~--- get methods ---------------------------------------------------------- /** diff --git a/scm-core/src/main/java/sonia/scm/web/security/WebSecurityContext.java b/scm-core/src/main/java/sonia/scm/web/security/WebSecurityContext.java index 1b9ccec280..d99716a5ff 100644 --- a/scm-core/src/main/java/sonia/scm/web/security/WebSecurityContext.java +++ b/scm-core/src/main/java/sonia/scm/web/security/WebSecurityContext.java @@ -44,11 +44,14 @@ import java.util.Collection; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.shiro.SecurityUtils; /** * * @author Sebastian Sdorra + * @deprecated use {@link SecurityUtils#getSecurityManager()} instead. */ +@Deprecated public interface WebSecurityContext extends SecurityContext { diff --git a/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java b/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java index ae485a66bc..9c99e6a7db 100644 --- a/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java @@ -36,6 +36,7 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import sonia.scm.config.ScmConfiguration; @@ -58,6 +59,7 @@ import java.util.Set; * * @author Sebastian Sdorra */ +@Ignore public class PermissionUtilTest { @@ -111,10 +113,8 @@ public class PermissionUtilTest Permission[] permissions = new Permission[] { new Permission("dent", PermissionType.READ), - new Permission("perfect", - PermissionType.WRITE), - new Permission("marvin", - PermissionType.OWNER) }; + new Permission("perfect", PermissionType.WRITE), + new Permission("marvin", PermissionType.OWNER) }; repository.setPermissions(Arrays.asList(permissions)); } @@ -139,10 +139,10 @@ public class PermissionUtilTest Repository r = new Repository(); r.setPermissions( - new ArrayList( - Arrays.asList( - new Permission("dent"), - new Permission("devel", PermissionType.WRITE, true), + new ArrayList( + Arrays.asList( + new Permission("dent"), + new Permission("devel", PermissionType.WRITE, true), new Permission("qa", PermissionType.READ, true)))); // member of both devel and qa @@ -167,9 +167,9 @@ public class PermissionUtilTest // member of no groups assertFalse(PermissionUtil.hasPermission(r, trillian, PermissionType.READ)); assertFalse(PermissionUtil.hasPermission(r, trillian, - PermissionType.WRITE)); + PermissionType.WRITE)); assertFalse(PermissionUtil.hasPermission(r, trillian, - PermissionType.OWNER)); + PermissionType.OWNER)); } /** @@ -207,29 +207,29 @@ public class PermissionUtilTest public void hasPermissionTest() { assertTrue(PermissionUtil.hasPermission(repository, dent, - PermissionType.READ)); + PermissionType.READ)); assertTrue(PermissionUtil.hasPermission(repository, perfect, - PermissionType.READ)); + PermissionType.READ)); assertTrue(PermissionUtil.hasPermission(repository, perfect, - PermissionType.WRITE)); + PermissionType.WRITE)); assertFalse(PermissionUtil.hasPermission(repository, dent, - PermissionType.WRITE)); + PermissionType.WRITE)); assertFalse(PermissionUtil.hasPermission(repository, slarti, - PermissionType.WRITE)); + PermissionType.WRITE)); assertFalse(PermissionUtil.hasPermission(repository, slarti, - PermissionType.READ)); + PermissionType.READ)); assertTrue(PermissionUtil.hasPermission(repository, marvin, - PermissionType.READ)); + PermissionType.READ)); assertTrue(PermissionUtil.hasPermission(repository, marvin, - PermissionType.WRITE)); + PermissionType.WRITE)); assertTrue(PermissionUtil.hasPermission(repository, marvin, - PermissionType.OWNER)); + PermissionType.OWNER)); assertTrue(PermissionUtil.hasPermission(repository, admams, - PermissionType.READ)); + PermissionType.READ)); assertTrue(PermissionUtil.hasPermission(repository, admams, - PermissionType.WRITE)); + PermissionType.WRITE)); assertTrue(PermissionUtil.hasPermission(repository, admams, - PermissionType.OWNER)); + PermissionType.OWNER)); } //~--- methods -------------------------------------------------------------- diff --git a/scm-core/src/test/java/sonia/scm/security/RepositoryPermissionTest.java b/scm-core/src/test/java/sonia/scm/security/RepositoryPermissionTest.java new file mode 100644 index 0000000000..e8180ca24a --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/security/RepositoryPermissionTest.java @@ -0,0 +1,87 @@ +/** + * 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.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.junit.Test; + +import sonia.scm.repository.PermissionType; + +import static org.junit.Assert.*; + +/** + * + * @author Sebastian Sdorra + */ +public class RepositoryPermissionTest +{ + + /** + * Method description + * + */ + @Test + public void testImplies() + { + RepositoryPermission p = new RepositoryPermission("asd", + PermissionType.READ); + + assertTrue(p.implies(new RepositoryPermission("asd", PermissionType.READ))); + assertFalse(p.implies(new RepositoryPermission("asd", + PermissionType.OWNER))); + assertFalse(p.implies(new RepositoryPermission("asd", + PermissionType.WRITE))); + p = new RepositoryPermission("asd", PermissionType.OWNER); + assertTrue(p.implies(new RepositoryPermission("asd", PermissionType.READ))); + assertFalse(p.implies(new RepositoryPermission("bdb", + PermissionType.READ))); + } + + /** + * Method description + * + */ + @Test + public void testImpliesWithWildcard() + { + RepositoryPermission p = new RepositoryPermission("*", + PermissionType.OWNER); + + assertTrue(p.implies(new RepositoryPermission("asd", PermissionType.READ))); + assertTrue(p.implies(new RepositoryPermission("bdb", + PermissionType.OWNER))); + assertTrue(p.implies(new RepositoryPermission("cgd", + PermissionType.WRITE))); + } +} diff --git a/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java b/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java index b9fd7e9da6..9eb47aa061 100644 --- a/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java +++ b/scm-core/src/test/java/sonia/scm/template/TemplateEngineFactoryTest.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; //~--- non-JDK imports -------------------------------------------------------- diff --git a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBStoreFactory.java b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBStoreFactory.java index 4fd7ead87b..3291be4806 100644 --- a/scm-dao-xml/src/main/java/sonia/scm/store/JAXBStoreFactory.java +++ b/scm-dao-xml/src/main/java/sonia/scm/store/JAXBStoreFactory.java @@ -91,7 +91,7 @@ public class JAXBStoreFactory implements ListenableStoreFactory public void init(SCMContextProvider context) { configDirectory = new File(context.getBaseDirectory(), - CONFIGDIRECTORY_NAME); + CONFIGDIRECTORY_NAME); IOUtil.mkdirs(configDirectory); } @@ -110,12 +110,17 @@ public class JAXBStoreFactory implements ListenableStoreFactory @Override public JAXBStore getStore(Class type, String name) { + if (configDirectory == null) + { + throw new IllegalStateException("store factory is not initialized"); + } + File configFile = new File(configDirectory, name.concat(FILE_EXTENSION)); if (logger.isDebugEnabled()) { logger.debug("create store for {} at {}", type.getName(), - configFile.getPath()); + configFile.getPath()); } return new JAXBStore(type, configFile); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java index 6842f24566..e10568b057 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitPermissionFilter.java @@ -41,7 +41,6 @@ import com.google.inject.Singleton; import sonia.scm.repository.RepositoryProvider; import sonia.scm.web.filter.ProviderPermissionFilter; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -81,10 +80,9 @@ public class GitPermissionFilter extends ProviderPermissionFilter @Inject public GitPermissionFilter( ScmConfiguration configuration, - Provider securityContextProvider, RepositoryProvider repositoryProvider) { - super(configuration, securityContextProvider, repositoryProvider); + super(configuration, repositoryProvider); } //~--- get methods ---------------------------------------------------------- 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 119ea7fcff..23ba07ffc4 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 @@ -39,6 +39,9 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,9 +54,9 @@ import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryNotFoundException; import sonia.scm.repository.RepositoryUtil; import sonia.scm.security.CipherUtil; +import sonia.scm.security.Tokens; import sonia.scm.util.HttpUtil; import sonia.scm.util.Util; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -120,16 +123,14 @@ public class HgHookCallbackServlet extends HttpServlet * @param securityContextProvider */ @Inject - public HgHookCallbackServlet( - RepositoryManager repositoryManager, HgRepositoryHandler handler, - HgHookManager hookManager, Provider contextProvider, - Provider securityContextProvider) + public HgHookCallbackServlet(RepositoryManager repositoryManager, + HgRepositoryHandler handler, HgHookManager hookManager, + Provider contextProvider) { this.repositoryManager = repositoryManager; this.handler = handler; this.hookManager = hookManager; this.contextProvider = contextProvider; - this.securityContextProvider = securityContextProvider; } //~--- methods -------------------------------------------------------------- @@ -146,7 +147,7 @@ public class HgHookCallbackServlet extends HttpServlet */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException + throws ServletException, IOException { String ping = request.getParameter(PARAM_PING); @@ -172,8 +173,8 @@ public class HgHookCallbackServlet extends HttpServlet */ @Override protected void doPost(HttpServletRequest request, - HttpServletResponse response) - throws ServletException, IOException + HttpServletResponse response) + throws ServletException, IOException { String strippedURI = HttpUtil.getStrippedURI(request); Matcher m = REGEX_URL.matcher(strippedURI); @@ -194,7 +195,7 @@ public class HgHookCallbackServlet extends HttpServlet if (Util.isNotEmpty(credentials)) { - authenticate(request, response, credentials); + authenticate(request, credentials); } hookCallback(response, repositoryId, type, challenge, node); @@ -228,8 +229,7 @@ public class HgHookCallbackServlet extends HttpServlet * @param response * @param credentials */ - private void authenticate(HttpServletRequest request, - HttpServletResponse response, String credentials) + private void authenticate(HttpServletRequest request, String credentials) { try { @@ -241,10 +241,10 @@ public class HgHookCallbackServlet extends HttpServlet if (credentialsArray.length >= 2) { - WebSecurityContext context = securityContextProvider.get(); + Subject subject = SecurityUtils.getSubject(); - context.authenticate(request, response, credentialsArray[0], - credentialsArray[1]); + subject.login(Tokens.createAuthenticationToken(request, + credentialsArray[0], credentialsArray[1])); } } } @@ -266,8 +266,8 @@ public class HgHookCallbackServlet extends HttpServlet * @throws IOException */ private void fireHook(HttpServletResponse response, String repositoryName, - String node, RepositoryHookType type) - throws IOException + String node, RepositoryHookType type) + throws IOException { try { @@ -277,9 +277,8 @@ public class HgHookCallbackServlet extends HttpServlet } repositoryManager.fireHookEvent(HgRepositoryHandler.TYPE_NAME, - repositoryName, - new HgRepositoryHookEvent(handler, - repositoryName, node, type)); + repositoryName, + new HgRepositoryHookEvent(handler, repositoryName, node, type)); } catch (RepositoryNotFoundException ex) { @@ -310,9 +309,8 @@ public class HgHookCallbackServlet extends HttpServlet * @throws IOException */ private void hookCallback(HttpServletResponse response, - String repositoryName, String typeName, - String challenge, String node) - throws IOException + String repositoryName, String typeName, String challenge, String node) + throws IOException { if (hookManager.isAcceptAble(challenge)) { @@ -404,7 +402,4 @@ public class HgHookCallbackServlet extends HttpServlet /** Field description */ private RepositoryManager repositoryManager; - - /** Field description */ - private Provider securityContextProvider; } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgPermissionFilter.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgPermissionFilter.java index 5df19d5042..73d20530ea 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgPermissionFilter.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgPermissionFilter.java @@ -36,17 +36,15 @@ 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.config.ScmConfiguration; import sonia.scm.repository.RepositoryProvider; import sonia.scm.web.filter.ProviderPermissionFilter; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ import javax.servlet.http.HttpServletRequest; -import sonia.scm.config.ScmConfiguration; /** * @@ -61,15 +59,15 @@ public class HgPermissionFilter extends ProviderPermissionFilter * * * @param securityContextProvider + * + * @param configuration * @param repositoryProvider */ @Inject - public HgPermissionFilter( - ScmConfiguration configuration, - Provider securityContextProvider, - RepositoryProvider repositoryProvider) + public HgPermissionFilter(ScmConfiguration configuration, + RepositoryProvider repositoryProvider) { - super(configuration, securityContextProvider, repositoryProvider); + super(configuration, repositoryProvider); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnPermissionFilter.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnPermissionFilter.java index d5a8941552..164ccfd173 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnPermissionFilter.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnPermissionFilter.java @@ -37,13 +37,11 @@ package sonia.scm.web; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; import sonia.scm.config.ScmConfiguration; import sonia.scm.repository.RepositoryProvider; import sonia.scm.web.filter.ProviderPermissionFilter; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -79,12 +77,10 @@ public class SvnPermissionFilter extends ProviderPermissionFilter * @param repository */ @Inject - public SvnPermissionFilter( - ScmConfiguration configuration, - Provider securityContextProvider, - RepositoryProvider repository) + public SvnPermissionFilter(ScmConfiguration configuration, + RepositoryProvider repository) { - super(configuration, securityContextProvider, repository); + super(configuration, repository); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-samples/scm-sample-hello/src/main/java/sample/hello/HelloResource.java b/scm-samples/scm-sample-hello/src/main/java/sample/hello/HelloResource.java index a83024f4bd..2064c7ffc0 100644 --- a/scm-samples/scm-sample-hello/src/main/java/sample/hello/HelloResource.java +++ b/scm-samples/scm-sample-hello/src/main/java/sample/hello/HelloResource.java @@ -35,10 +35,10 @@ package sample.hello; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Inject; -import com.google.inject.Provider; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; -import sonia.scm.security.SecurityContext; +import sonia.scm.user.User; //~--- JDK imports ------------------------------------------------------------ @@ -61,11 +61,18 @@ public class HelloResource * * @param securityContextProvider */ - @Inject - public HelloResource(Provider securityContextProvider) + public HelloResource() { - message = "Hello " - + securityContextProvider.get().getUser().getDisplayName(); + Subject subject = SecurityUtils.getSubject(); + String displayName = "Unknown"; + + if (subject.isAuthenticated()) + { + displayName = + subject.getPrincipals().oneByType(User.class).getDisplayName(); + } + + message = "Hello " + displayName; } //~--- get methods ---------------------------------------------------------- diff --git a/scm-test/src/main/java/sonia/scm/AbstractTestBase.java b/scm-test/src/main/java/sonia/scm/AbstractTestBase.java index d35ac129ee..2a8a43ad7a 100644 --- a/scm-test/src/main/java/sonia/scm/AbstractTestBase.java +++ b/scm-test/src/main/java/sonia/scm/AbstractTestBase.java @@ -35,7 +35,15 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.UnavailableSecurityManagerException; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.LifecycleUtils; +import org.apache.shiro.util.ThreadState; + import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; import sonia.scm.util.IOUtil; @@ -56,6 +64,81 @@ import java.util.UUID; public class AbstractTestBase { + /** Field description */ + private static ThreadState subjectThreadState; + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @AfterClass + public static void tearDownShiro() + { + doClearSubject(); + + try + { + org.apache.shiro.mgt.SecurityManager securityManager = + getSecurityManager(); + + LifecycleUtils.destroy(securityManager); + } + catch (UnavailableSecurityManagerException e) + { + + // we don't care about this when cleaning up the test environment + // (for example, maybe the subclass is a unit test and it didn't + // need a SecurityManager instance because it was using only + // mock Subject instances) + } + + setSecurityManager(null); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + protected static org.apache.shiro.mgt.SecurityManager getSecurityManager() + { + return SecurityUtils.getSecurityManager(); + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param securityManager + */ + protected static void setSecurityManager( + org.apache.shiro.mgt.SecurityManager securityManager) + { + SecurityUtils.setSecurityManager(securityManager); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + private static void doClearSubject() + { + if (subjectThreadState != null) + { + subjectThreadState.clear(); + subjectThreadState = null; + } + } + /** * Method description * @@ -87,7 +170,7 @@ public class AbstractTestBase public void setUpTest() throws Exception { tempDirectory = new File(System.getProperty("java.io.tmpdir"), - UUID.randomUUID().toString()); + UUID.randomUUID().toString()); assertTrue(tempDirectory.mkdirs()); contextProvider = MockUtil.getSCMContextProvider(tempDirectory); postSetUp(); @@ -95,6 +178,27 @@ public class AbstractTestBase //~--- methods -------------------------------------------------------------- + /** + * Clears Shiro's thread state, ensuring the thread remains clean for future test execution. + */ + protected void clearSubject() + { + doClearSubject(); + } + + /** + * Method description + * + * + * @param subject + * + * @return + */ + protected ThreadState createThreadState(Subject subject) + { + return new SubjectThreadState(subject); + } + /** * Method description * @@ -111,6 +215,33 @@ public class AbstractTestBase */ protected void preTearDown() throws Exception {} + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + protected Subject getSubject() + { + return SecurityUtils.getSubject(); + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Allows subclasses to set the currently executing {@link Subject} instance. + * + * @param subject the Subject instance + */ + protected void setSubject(Subject subject) + { + clearSubject(); + subjectThreadState = createThreadState(subject); + subjectThreadState.bind(); + } + ; //~--- fields --------------------------------------------------------------- diff --git a/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java b/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java index f3594e2987..2841eafe72 100644 --- a/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java +++ b/scm-test/src/main/java/sonia/scm/user/UserManagerTestBase.java @@ -35,6 +35,9 @@ package sonia.scm.user; //~--- non-JDK imports -------------------------------------------------------- +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + import org.junit.Test; import sonia.scm.Manager; @@ -56,7 +59,7 @@ import java.util.UUID; * @author Sebastian Sdorra */ public abstract class UserManagerTestBase - extends ManagerTestBase + extends ManagerTestBase { /** Field description */ @@ -265,7 +268,7 @@ public abstract class UserManagerTestBase */ @Test public void testMultiThreaded() - throws UserException, IOException, InterruptedException + throws UserException, IOException, InterruptedException { int initialSize = manager.getAll().size(); List testers = new ArrayList(); @@ -275,8 +278,11 @@ public abstract class UserManagerTestBase testers.add(new MultiThreadTester(manager)); } + Subject subject = SecurityUtils.getSubject(); + for (MultiThreadTester tester : testers) { + subject.associateWith(tester); new Thread(tester).start(); } @@ -393,7 +399,7 @@ public abstract class UserManagerTestBase { String id = UUID.randomUUID().toString(); User user = new User(id, id.concat(" displayName"), - id.concat("@mail.com")); + id.concat("@mail.com")); manager.create(user); @@ -410,7 +416,7 @@ public abstract class UserManagerTestBase * @throws UserException */ private void modifyAndDeleteUser(User user) - throws UserException, IOException + throws UserException, IOException { String name = user.getName(); String nd = name.concat(" new displayname"); diff --git a/scm-test/src/main/java/sonia/scm/util/MockUtil.java b/scm-test/src/main/java/sonia/scm/util/MockUtil.java index ba6d9c74f5..bb6a0a1ca8 100644 --- a/scm-test/src/main/java/sonia/scm/util/MockUtil.java +++ b/scm-test/src/main/java/sonia/scm/util/MockUtil.java @@ -37,6 +37,13 @@ package sonia.scm.util; import com.google.inject.Provider; +import org.apache.shiro.authz.Permission; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.Subject; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + import sonia.scm.SCMContextProvider; import sonia.scm.user.User; import sonia.scm.web.security.DummyWebSecurityContext; @@ -48,6 +55,9 @@ import static org.mockito.Mockito.*; import java.io.File; +import java.util.Arrays; +import java.util.List; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -58,6 +68,59 @@ import javax.servlet.http.HttpServletResponse; public class MockUtil { + /** Field description */ + private static final User ADMIN = new User("scmadmin", "SCM Admin", + "scmadmin@scm.org"); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public static Subject createAdminSubject() + { + Subject subject = mock(Subject.class); + + when(subject.isAuthenticated()).thenReturn(Boolean.TRUE); + when(subject.isPermitted(anyListOf(Permission.class))).then( + new Answer() + { + + @Override + public Boolean[] answer(InvocationOnMock invocation) throws Throwable + { + List permissions = + (List) invocation.getArguments()[0]; + Boolean[] returnArray = new Boolean[permissions.size()]; + + Arrays.fill(returnArray, Boolean.TRUE); + + return returnArray; + } + }); + when(subject.isPermitted(any(Permission.class))).thenReturn(Boolean.TRUE); + when(subject.isPermitted(any(String.class))).thenReturn(Boolean.TRUE); + when(subject.isPermittedAll(anyCollectionOf(Permission.class))).thenReturn( + Boolean.TRUE); + when(subject.isPermittedAll()).thenReturn(Boolean.TRUE); + when(subject.hasRole("admin")).thenReturn(Boolean.TRUE); + + PrincipalCollection collection = mock(PrincipalCollection.class); + + when(collection.getPrimaryPrincipal()).thenReturn(ADMIN.getId()); + when(collection.oneByType(User.class)).thenReturn(ADMIN); + + when(subject.getPrincipal()).thenReturn(ADMIN.getId()); + when(subject.getPrincipals()).thenReturn(collection); + + return subject; + } + + //~--- get methods ---------------------------------------------------------- + /** * Method description * diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index e08eb823fb..6fde5319b1 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -64,6 +64,20 @@ scm-git-plugin 1.21-SNAPSHOT + + + + + org.apache.shiro + shiro-web + ${shiro.version} + + + + org.apache.shiro + shiro-guice + ${shiro.version} + diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index 54b146a4d8..2a876502a2 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -35,11 +35,14 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.collect.Lists; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.servlet.GuiceServletContextListener; +import org.apache.shiro.guice.web.ShiroWebModule; + import sonia.scm.cache.CacheManager; import sonia.scm.group.GroupManager; import sonia.scm.plugin.DefaultPluginLoader; @@ -49,13 +52,12 @@ import sonia.scm.store.StoreFactory; import sonia.scm.user.UserManager; import sonia.scm.util.IOUtil; import sonia.scm.web.security.AuthenticationManager; -import sonia.scm.web.security.LocalSecurityContextHolder; //~--- JDK imports ------------------------------------------------------------ -import java.util.ArrayList; import java.util.List; +import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; /** @@ -95,13 +97,10 @@ public class ScmContextListener extends GuiceServletContextListener // close CacheManager IOUtil.close(globalInjector.getInstance(CacheManager.class)); - // remove thread local store - globalInjector.getInstance(LocalSecurityContextHolder.class).destroy(); - // call destroy event globalInjector.getInstance( - ServletContextListenerHolder.class).contextDestroyed( - servletContextEvent); + ServletContextListenerHolder.class).contextDestroyed( + servletContextEvent); } super.contextDestroyed(servletContextEvent); @@ -116,6 +115,8 @@ public class ScmContextListener extends GuiceServletContextListener @Override public void contextInitialized(ServletContextEvent servletContextEvent) { + this.servletContext = servletContextEvent.getServletContext(); + if (SCMContext.getContext().getStartupError() == null) { ScmUpgradeHandler upgradeHandler = new ScmUpgradeHandler(); @@ -133,8 +134,8 @@ public class ScmContextListener extends GuiceServletContextListener if ((globalInjector != null) &&!startupError) { globalInjector.getInstance( - ServletContextListenerHolder.class).contextInitialized( - servletContextEvent); + ServletContextListenerHolder.class).contextInitialized( + servletContextEvent); } } @@ -155,7 +156,7 @@ public class ScmContextListener extends GuiceServletContextListener } else { - globalInjector = getDefaultInjector(); + globalInjector = getDefaultInjector(servletContext); } return globalInjector; @@ -165,9 +166,11 @@ public class ScmContextListener extends GuiceServletContextListener * Method description * * + * + * @param servletContext * @return */ - private Injector getDefaultInjector() + private Injector getDefaultInjector(ServletContext servletContext) { PluginLoader pluginLoader = new DefaultPluginLoader(); BindingExtensionProcessor bindExtProcessor = @@ -178,42 +181,16 @@ public class ScmContextListener extends GuiceServletContextListener ClassOverrides overrides = ClassOverrides.findOverrides(); ScmServletModule main = new ScmServletModule(pluginLoader, bindExtProcessor, overrides); - List moduleList = new ArrayList(); + List moduleList = Lists.newArrayList(); + moduleList.add(new ScmInitializerModule()); + moduleList.add(ShiroWebModule.guiceFilterModule()); + moduleList.add(main); + moduleList.add(new ScmSecurityModule(servletContext)); moduleList.addAll(bindExtProcessor.getModuleSet()); moduleList.addAll(overrides.getModules()); - moduleList.add(0, main); - Injector injector = Guice.createInjector(moduleList); - SCMContextProvider context = SCMContext.getContext(); - - // init StoreFactory - injector.getInstance(StoreFactory.class).init(context); - - // init RepositoryManager - RepositoryManager repositoryManager = - injector.getInstance(RepositoryManager.class); - - repositoryManager.addHooks(bindExtProcessor.getHooks()); - repositoryManager.init(context); - - // init UserManager - UserManager userManager = injector.getInstance(UserManager.class); - - userManager.init(context); - - // init GroupManager - GroupManager groupManager = injector.getInstance(GroupManager.class); - - groupManager.init(context); - - // init Authenticator - AuthenticationManager authenticationManager = - injector.getInstance(AuthenticationManager.class); - - authenticationManager.init(context); - - return injector; + return Guice.createInjector(moduleList); } /** @@ -232,6 +209,9 @@ public class ScmContextListener extends GuiceServletContextListener /** Field description */ private Injector globalInjector; + /** Field description */ + private ServletContext servletContext; + /** Field description */ private boolean startupError = false; } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmInitializerModule.java b/scm-webapp/src/main/java/sonia/scm/ScmInitializerModule.java new file mode 100644 index 0000000000..16675749ac --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/ScmInitializerModule.java @@ -0,0 +1,149 @@ +/** + * 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; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.AbstractModule; +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.AbstractMatcher; +import com.google.inject.matcher.Matcher; +import com.google.inject.spi.InjectionListener; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Sebastian Sdorra + */ +public class ScmInitializerModule extends AbstractModule +{ + + /** + * the logger for ScmInitializerModule + */ + private static final Logger logger = + LoggerFactory.getLogger(ScmInitializerModule.class); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + @Override + protected void configure() + { + bindListener(isSubtypeOf(Initable.class), new TypeListener() + { + + @Override + public void hear(TypeLiteral type, TypeEncounter encounter) + { + encounter.register(new InjectionListener() + { + @Override + public void afterInjection(Object i) + { + if (logger.isTraceEnabled()) + { + logger.trace("initialize initable {}", i.getClass()); + } + + Initable initable = (Initable) i; + + initable.init(SCMContext.getContext()); + } + }); + } + }); + } + + /** + * Method description + * + * + * @param subtype + * @param supertype + * + * @return + */ + private boolean typeIsSubtypeOf(TypeLiteral subtype, + TypeLiteral supertype) + { + + // First check that raw types are compatible + // Then check that generic types are compatible! HOW???? + return (subtype.equals(supertype) + || (supertype.getRawType().isAssignableFrom(subtype.getRawType()) + && supertype.equals(subtype.getSupertype(supertype.getRawType())))); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param supertype + * + * @return + */ + private Matcher> isSubtypeOf(final Class supertype) + { + return isSubtypeOf(TypeLiteral.get(supertype)); + } + + /** + * Method description + * + * + * @param supertype + * + * @return + */ + private Matcher> isSubtypeOf(final TypeLiteral supertype) + { + return new AbstractMatcher>() + { + @Override + public boolean matches(TypeLiteral type) + { + return typeIsSubtypeOf(type, supertype); + } + }; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/LocalSecurityContextHolder.java b/scm-webapp/src/main/java/sonia/scm/ScmSecurityModule.java similarity index 68% rename from scm-webapp/src/main/java/sonia/scm/web/security/LocalSecurityContextHolder.java rename to scm-webapp/src/main/java/sonia/scm/ScmSecurityModule.java index 6f0277f25a..50d3184b3a 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/LocalSecurityContextHolder.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmSecurityModule.java @@ -30,67 +30,45 @@ */ +package sonia.scm; -package sonia.scm.web.security; +//~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Singleton; +import org.apache.shiro.guice.web.ShiroWebModule; + +import sonia.scm.security.ScmRealm; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.ServletContext; /** * * @author Sebastian Sdorra */ -@Singleton -public class LocalSecurityContextHolder +public class ScmSecurityModule extends ShiroWebModule { /** - * Method description + * Constructs ... * + * + * @param servletContext */ - public void destroy() + ScmSecurityModule(ServletContext servletContext) { - store.remove(); - store = null; + super(servletContext); } + //~--- methods -------------------------------------------------------------- + /** * Method description * */ - public void remove() + @Override + protected void configureShiroWeb() { - store.remove(); + bindRealm().to(ScmRealm.class); } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public WebSecurityContext get() - { - return store.get(); - } - - //~--- set methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @param value - */ - public void set(WebSecurityContext value) - { - store.set(value); - } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private ThreadLocal store = - new ThreadLocal(); } diff --git a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java index 5a9da3c222..c20aad948e 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmServletModule.java @@ -116,8 +116,6 @@ import sonia.scm.web.security.AuthenticationManager; import sonia.scm.web.security.BasicSecurityContext; import sonia.scm.web.security.ChainAuthenticatonManager; import sonia.scm.web.security.DefaultAdministrationContext; -import sonia.scm.web.security.LocalSecurityContextHolder; -import sonia.scm.web.security.SecurityContextProvider; import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -264,11 +262,8 @@ public class ScmServletModule extends ServletModule // bind security stuff bind(AuthenticationManager.class, ChainAuthenticatonManager.class); - bind(LocalSecurityContextHolder.class); - bind(WebSecurityContext.class).annotatedWith(Names.named("userSession")).to( - BasicSecurityContext.class); - bind(SecurityContext.class).toProvider(SecurityContextProvider.class); - bind(WebSecurityContext.class).toProvider(SecurityContextProvider.class); + bind(SecurityContext.class).to(BasicSecurityContext.class); + bind(WebSecurityContext.class).to(BasicSecurityContext.class); bind(AdministrationContext.class, DefaultAdministrationContext.class); // bind security cache 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 9dc0277fc7..150d352219 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 @@ -36,9 +36,13 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.Subject; + import org.codehaus.enunciate.jaxrs.TypeHint; import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; @@ -50,13 +54,17 @@ import sonia.scm.SCMContextProvider; import sonia.scm.ScmClientConfig; import sonia.scm.ScmState; import sonia.scm.config.ScmConfiguration; +import sonia.scm.group.GroupNames; import sonia.scm.repository.RepositoryManager; +import sonia.scm.security.Tokens; import sonia.scm.user.User; import sonia.scm.user.UserManager; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ +import java.util.Collection; +import java.util.Collections; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -98,16 +106,14 @@ public class AuthenticationResource * @param securityContextProvider */ @Inject - public AuthenticationResource( - SCMContextProvider contextProvider, ScmConfiguration configuration, - RepositoryManager repositoryManger, UserManager userManager, - Provider securityContextProvider) + public AuthenticationResource(SCMContextProvider contextProvider, + ScmConfiguration configuration, RepositoryManager repositoryManger, + UserManager userManager) { this.contextProvider = contextProvider; this.configuration = configuration; this.repositoryManger = repositoryManger; this.userManager = userManager; - this.securityContextProvider = securityContextProvider; } //~--- methods -------------------------------------------------------------- @@ -132,21 +138,30 @@ public class AuthenticationResource @Path("login") @TypeHint(ScmState.class) public ScmState authenticate(@Context HttpServletRequest request, - @Context HttpServletResponse response, - @FormParam("username") String username, - @FormParam("password") String password) + @FormParam("username") String username, + @FormParam("password") String password) { ScmState state = null; - WebSecurityContext securityContext = securityContextProvider.get(); - User user = securityContext.authenticate(request, response, username, - password); - if ((user != null) &&!SCMContext.USER_ANONYMOUS.equals(user.getName())) + Subject subject = SecurityUtils.getSubject(); + + try { - state = createState(securityContext); + subject.login(Tokens.createAuthenticationToken(request, username, + password)); + state = createState(subject); } - else + catch (AuthenticationException ex) { + if (logger.isTraceEnabled()) + { + logger.trace("authentication failed for user ".concat(username), ex); + } + else if (logger.isWarnEnabled()) + { + logger.warn("authentication failed for user {}", username); + } + throw new WebApplicationException(Response.Status.UNAUTHORIZED); } @@ -171,20 +186,18 @@ public class AuthenticationResource @Path("logout") @TypeHint(ScmState.class) public Response logout(@Context HttpServletRequest request, - @Context HttpServletResponse response) + @Context HttpServletResponse response) { - WebSecurityContext securityContext = securityContextProvider.get(); + Subject subject = SecurityUtils.getSubject(); - securityContext.logout(request, response); + subject.logout(); Response resp = null; - User user = securityContext.getUser(); - if (user != null) + if (configuration.isAnonymousAccessEnabled()) { - ScmState state = createState(securityContext); - resp = Response.ok(state).build(); + resp = Response.ok(createAnonymousState()).build(); } else { @@ -238,20 +251,24 @@ public class AuthenticationResource public Response getState(@Context HttpServletRequest request) { Response response = null; - ScmState state = null; - WebSecurityContext securityContext = securityContextProvider.get(); - User user = securityContext.getUser(); + Subject subject = SecurityUtils.getSubject(); - if (user != null) + if (subject.isAuthenticated()) { if (logger.isDebugEnabled()) { - logger.debug("return state for user {}", user.getName()); + logger.debug("return state for user {}", subject.getPrincipal()); } - state = createState(securityContext); + ScmState state = createState(subject); + response = Response.ok(state).build(); } + else if (configuration.isAnonymousAccessEnabled()) + { + + response = Response.ok(createAnonymousState()).build(); + } else { response = Response.status(Response.Status.UNAUTHORIZED).build(); @@ -262,20 +279,50 @@ public class AuthenticationResource //~--- methods -------------------------------------------------------------- + /** + * Method description + * + * + * @return + */ + private ScmState createAnonymousState() + { + return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST); + } + /** * Method description * * * @param securityContext * + * @param subject + * * @return */ - private ScmState createState(WebSecurityContext securityContext) + private ScmState createState(Subject subject) { - return new ScmState(contextProvider, securityContext, - repositoryManger.getConfiguredTypes(), - userManager.getDefaultType(), - new ScmClientConfig(configuration)); + PrincipalCollection collection = subject.getPrincipals(); + User user = collection.oneByType(User.class); + GroupNames groups = collection.oneByType(GroupNames.class); + + 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)); } //~--- fields --------------------------------------------------------------- @@ -289,9 +336,6 @@ public class AuthenticationResource /** Field description */ private RepositoryManager repositoryManger; - /** Field description */ - private Provider securityContextProvider; - /** Field description */ private UserManager userManager; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java index 9b4b05c43e..48c63b3b0a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java @@ -36,7 +36,9 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; import org.codehaus.enunciate.jaxrs.TypeHint; import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; @@ -46,11 +48,11 @@ import org.slf4j.LoggerFactory; import sonia.scm.api.rest.RestActionResult; import sonia.scm.security.EncryptionHandler; +import sonia.scm.security.ScmSecurityException; import sonia.scm.user.User; import sonia.scm.user.UserException; import sonia.scm.user.UserManager; import sonia.scm.util.AssertUtil; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -88,13 +90,11 @@ public class ChangePasswordResource * @param securityContextProvider */ @Inject - public ChangePasswordResource( - UserManager userManager, EncryptionHandler encryptionHandler, - Provider securityContextProvider) + public ChangePasswordResource(UserManager userManager, + EncryptionHandler encryptionHandler) { this.userManager = userManager; this.encryptionHandler = encryptionHandler; - this.securityContextProvider = securityContextProvider; } //~--- methods -------------------------------------------------------------- @@ -121,8 +121,8 @@ public class ChangePasswordResource @TypeHint(RestActionResult.class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response changePassword(@FormParam("old-password") String oldPassword, - @FormParam("new-password") String newPassword) - throws UserException, IOException + @FormParam("new-password") String newPassword) + throws UserException, IOException { AssertUtil.assertIsNotEmpty(oldPassword); AssertUtil.assertIsNotEmpty(newPassword); @@ -135,8 +135,14 @@ public class ChangePasswordResource } Response response = null; - WebSecurityContext securityContext = securityContextProvider.get(); - User currentUser = securityContext.getUser(); + Subject subject = SecurityUtils.getSubject(); + + if (!subject.isAuthenticated()) + { + throw new ScmSecurityException("user is not authenticated"); + } + + User currentUser = subject.getPrincipals().oneByType(User.class); if (logger.isInfoEnabled()) { @@ -178,9 +184,6 @@ public class ChangePasswordResource /** Field description */ private EncryptionHandler encryptionHandler; - /** Field description */ - private Provider securityContextProvider; - /** Field description */ private UserManager userManager; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ConfigurationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ConfigurationResource.java index 36cd0d149a..6c888470ad 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ConfigurationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ConfigurationResource.java @@ -36,15 +36,17 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.Role; +import sonia.scm.security.ScmSecurityException; import sonia.scm.util.ScmConfigurationUtil; -import sonia.scm.util.SecurityUtil; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -76,11 +78,8 @@ public class ConfigurationResource * @param securityContextProvider */ @Inject - public ConfigurationResource( - Provider securityContextProvider, - ScmConfiguration configuration) + public ConfigurationResource(ScmConfiguration configuration) { - this.securityContextProvider = securityContextProvider; this.configuration = configuration; } @@ -98,7 +97,7 @@ public class ConfigurationResource { Response response = null; - if (SecurityUtil.isAdmin(securityContextProvider)) + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { response = Response.ok(configuration).build(); } @@ -124,9 +123,17 @@ public class ConfigurationResource @POST @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public Response setConfig(@Context UriInfo uriInfo, - ScmConfiguration newConfig) + ScmConfiguration newConfig) { - SecurityUtil.assertIsAdmin(securityContextProvider); + + // TODO replace by checkRole + Subject subject = SecurityUtils.getSubject(); + + if (!subject.hasRole(Role.ADMIN)) + { + throw new ScmSecurityException("admin privileges required"); + } + configuration.load(newConfig); synchronized (ScmConfiguration.class) @@ -141,7 +148,4 @@ public class ConfigurationResource /** Field description */ public ScmConfiguration configuration; - - /** Field description */ - private Provider securityContextProvider; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupResource.java index f542195bf4..f1a4d19ff3 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/GroupResource.java @@ -39,14 +39,15 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; + import org.codehaus.enunciate.jaxrs.TypeHint; import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; import sonia.scm.group.Group; import sonia.scm.group.GroupException; import sonia.scm.group.GroupManager; -import sonia.scm.util.SecurityUtil; -import sonia.scm.web.security.WebSecurityContext; +import sonia.scm.security.Role; //~--- JDK imports ------------------------------------------------------------ @@ -77,7 +78,7 @@ import javax.ws.rs.core.UriInfo; @Singleton @ExternallyManagedLifecycle public class GroupResource - extends AbstractManagerResource + extends AbstractManagerResource { /** Field description */ @@ -94,11 +95,9 @@ public class GroupResource * @param groupManager */ @Inject - public GroupResource(Provider securityContextProvider, - GroupManager groupManager) + public GroupResource(GroupManager groupManager) { super(groupManager); - this.securityContextProvider = securityContextProvider; } //~--- methods -------------------------------------------------------------- @@ -172,7 +171,7 @@ public class GroupResource @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response update(@Context UriInfo uriInfo, - @PathParam("id") String name, Group group) + @PathParam("id") String name, Group group) { return super.update(uriInfo, name, group); } @@ -205,7 +204,7 @@ public class GroupResource { Response response = null; - if (SecurityUtil.isAdmin(securityContextProvider)) + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { response = super.get(request, id); } @@ -243,7 +242,7 @@ public class GroupResource public Response getAll(@Context Request request, @DefaultValue("0") @QueryParam("start") int start, @DefaultValue("-1") @QueryParam("limit") int limit, @QueryParam("sortby") String sortby, - @DefaultValue("false") + @DefaultValue("false") @QueryParam("desc") boolean desc) { return super.getAll(request, start, limit, sortby, desc); @@ -261,7 +260,7 @@ public class GroupResource */ @Override protected GenericEntity> createGenericEntity( - Collection items) + Collection items) { return new GenericEntity>(items) {} ; @@ -294,9 +293,4 @@ public class GroupResource { return PATH_PART; } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private Provider securityContextProvider; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java index 4462f28c2d..b3a994112a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryImportResource.java @@ -30,12 +30,12 @@ */ + package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; import org.codehaus.enunciate.jaxrs.TypeHint; @@ -50,7 +50,6 @@ import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryHandler; import sonia.scm.repository.RepositoryManager; import sonia.scm.util.SecurityUtil; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -93,12 +92,9 @@ public class RepositoryImportResource * @param securityContextProvider */ @Inject - public RepositoryImportResource( - RepositoryManager manager, - Provider securityContextProvider) + public RepositoryImportResource(RepositoryManager manager) { this.manager = manager; - this.securityContextProvider = securityContextProvider; } //~--- methods -------------------------------------------------------------- @@ -116,9 +112,9 @@ public class RepositoryImportResource @TypeHint(Repository[].class) @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public GenericEntity> importRepositories( - @PathParam("type") String type) + @PathParam("type") String type) { - SecurityUtil.assertIsAdmin(securityContextProvider); + SecurityUtil.assertIsAdmin(); List repositories = new ArrayList(); RepositoryHandler handler = manager.getHandler(type); @@ -143,7 +139,7 @@ public class RepositoryImportResource else if (logger.isWarnEnabled()) { logger.warn("could not find imported repository {}", - repositoryName); + repositoryName); } } } @@ -175,7 +171,7 @@ public class RepositoryImportResource @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) public GenericEntity> getImportableTypes() { - SecurityUtil.assertIsAdmin(securityContextProvider); + SecurityUtil.assertIsAdmin(); List types = new ArrayList(); Collection handlerTypes = manager.getTypes(); @@ -202,7 +198,7 @@ public class RepositoryImportResource else if (logger.isInfoEnabled()) { logger.info("{} handler does not support import of repositories", - t.getName()); + t.getName()); } } } @@ -220,7 +216,4 @@ public class RepositoryImportResource /** Field description */ private RepositoryManager manager; - - /** Field description */ - private Provider securityContextProvider; } 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 a8adf22f6e..7801abf7b2 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 @@ -38,9 +38,10 @@ package sonia.scm.api.rest.resources; import com.google.common.base.Strings; import com.google.common.io.Closeables; import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; + import org.codehaus.enunciate.jaxrs.TypeHint; import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; @@ -55,7 +56,6 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.Permission; import sonia.scm.repository.PermissionType; -import sonia.scm.repository.PermissionUtil; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryException; import sonia.scm.repository.RepositoryIsNotArchivedException; @@ -71,10 +71,10 @@ import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.LogCommandBuilder; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; +import sonia.scm.security.RepositoryPermission; import sonia.scm.security.ScmSecurityException; import sonia.scm.util.AssertUtil; import sonia.scm.util.Util; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -137,14 +137,12 @@ public class RepositoryResource @Inject public RepositoryResource(ScmConfiguration configuration, RepositoryManager repositoryManager, - Provider securityContextProvider, RepositoryServiceFactory servicefactory) { super(repositoryManager); this.configuration = configuration; this.repositoryManager = repositoryManager; this.servicefactory = servicefactory; - this.securityContextProvider = securityContextProvider; setDisableCache(false); } @@ -1091,8 +1089,9 @@ public class RepositoryResource */ private boolean isOwner(Repository repository) { - return PermissionUtil.hasPermission(repository, securityContextProvider, - PermissionType.OWNER); + + return SecurityUtils.getSubject().isPermitted( + new RepositoryPermission(repository, PermissionType.OWNER)); } //~--- fields --------------------------------------------------------------- @@ -1103,9 +1102,6 @@ public class RepositoryResource /** Field description */ private RepositoryManager repositoryManager; - /** Field description */ - private Provider securityContextProvider; - /** Field description */ private RepositoryServiceFactory servicefactory; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java index e636b60852..0b01dffeaa 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SearchResource.java @@ -37,7 +37,6 @@ package sonia.scm.api.rest.resources; import com.google.common.base.Function; import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; @@ -54,7 +53,6 @@ import sonia.scm.search.SearchResults; import sonia.scm.user.User; import sonia.scm.user.UserListener; import sonia.scm.user.UserManager; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -92,9 +90,8 @@ public class SearchResource implements UserListener, GroupListener * @param cacheManager */ @Inject - public SearchResource(Provider securityContextProvider, - UserManager userManager, GroupManager groupManager, - CacheManager cacheManager) + public SearchResource(UserManager userManager, GroupManager groupManager, + CacheManager cacheManager) { // create user searchhandler @@ -103,8 +100,7 @@ public class SearchResource implements UserListener, GroupListener Cache userCache = cacheManager.getCache(String.class, SearchResults.class, CACHE_USER); - this.userSearchHandler = new SearchHandler(securityContextProvider, - userCache, userManager); + this.userSearchHandler = new SearchHandler(userCache, userManager); // create group searchhandler groupManager.addListener(this); @@ -112,8 +108,8 @@ public class SearchResource implements UserListener, GroupListener Cache groupCache = cacheManager.getCache(String.class, SearchResults.class, CACHE_GROUP); - this.groupSearchHandler = new SearchHandler(securityContextProvider, - groupCache, groupManager); + this.groupSearchHandler = new SearchHandler(groupCache, + groupManager); } //~--- methods -------------------------------------------------------------- @@ -162,7 +158,7 @@ public class SearchResource implements UserListener, GroupListener public SearchResults searchGroups(@QueryParam("query") String queryString) { return groupSearchHandler.search(queryString, - new Function() + new Function() { @Override public SearchResult apply(Group group) @@ -198,7 +194,7 @@ public class SearchResource implements UserListener, GroupListener public SearchResults searchUsers(@QueryParam("query") String queryString) { return userSearchHandler.search(queryString, - new Function() + new Function() { @Override public SearchResult apply(User user) diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java index 2472f81436..06f65c44d5 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/SupportResource.java @@ -39,6 +39,9 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.inject.Inject; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; import sonia.scm.SCMContextProvider; @@ -47,10 +50,10 @@ import sonia.scm.config.ScmConfiguration; import sonia.scm.plugin.PluginManager; import sonia.scm.repository.RepositoryHandler; import sonia.scm.repository.RepositoryManager; +import sonia.scm.security.Role; +import sonia.scm.security.ScmSecurityException; import sonia.scm.store.StoreFactory; -import sonia.scm.util.SecurityUtil; import sonia.scm.util.SystemUtil; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -96,12 +99,10 @@ public class SupportResource * @param repositoryManager */ @Inject - public SupportResource(WebSecurityContext securityContext, - SCMContextProvider context, ScmConfiguration configuration, - PluginManager pluginManager, StoreFactory storeFactory, - RepositoryManager repositoryManager) + public SupportResource(SCMContextProvider context, + ScmConfiguration configuration, PluginManager pluginManager, + StoreFactory storeFactory, RepositoryManager repositoryManager) { - this.securityContext = securityContext; this.context = context; this.configuration = configuration; this.pluginManager = pluginManager; @@ -123,7 +124,12 @@ public class SupportResource @Produces(MediaType.TEXT_HTML) public Viewable getSupport() throws IOException { - SecurityUtil.assertIsAdmin(securityContext); + Subject subject = SecurityUtils.getSubject(); + + if (!subject.hasRole(Role.ADMIN)) + { + throw new ScmSecurityException("admin privileges required"); + } Map env = Maps.newHashMap(); @@ -445,9 +451,6 @@ public class SupportResource /** Field description */ private RepositoryManager repositoryManager; - /** Field description */ - private WebSecurityContext securityContext; - /** Field description */ private Class storeFactoryClass; } diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserResource.java index 62deb10607..ad264fd0a8 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/UserResource.java @@ -36,20 +36,20 @@ package sonia.scm.api.rest.resources; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; + import org.codehaus.enunciate.jaxrs.TypeHint; import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; import sonia.scm.security.EncryptionHandler; +import sonia.scm.security.Role; import sonia.scm.user.User; import sonia.scm.user.UserException; import sonia.scm.user.UserManager; import sonia.scm.util.AssertUtil; -import sonia.scm.util.SecurityUtil; import sonia.scm.util.Util; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -100,12 +100,10 @@ public class UserResource extends AbstractManagerResource */ @Inject public UserResource(UserManager userManager, - EncryptionHandler encryptionHandler, - Provider securityContextProvider) + EncryptionHandler encryptionHandler) { super(userManager); this.encryptionHandler = encryptionHandler; - this.securityContextProvider = securityContextProvider; } //~--- methods -------------------------------------------------------------- @@ -179,7 +177,7 @@ public class UserResource extends AbstractManagerResource @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) @Override public Response update(@Context UriInfo uriInfo, - @PathParam("id") String name, User user) + @PathParam("id") String name, User user) { return super.update(uriInfo, name, user); } @@ -212,7 +210,7 @@ public class UserResource extends AbstractManagerResource { Response response = null; - if (SecurityUtil.isAdmin(securityContextProvider)) + if (SecurityUtils.getSubject().hasRole(Role.ADMIN)) { response = super.get(request, id); } @@ -250,7 +248,7 @@ public class UserResource extends AbstractManagerResource public Response getAll(@Context Request request, @DefaultValue("0") @QueryParam("start") int start, @DefaultValue("-1") @QueryParam("limit") int limit, @QueryParam("sortby") String sortby, - @DefaultValue("false") + @DefaultValue("false") @QueryParam("desc") boolean desc) { return super.getAll(request, start, limit, sortby, desc); @@ -268,7 +266,7 @@ public class UserResource extends AbstractManagerResource */ @Override protected GenericEntity> createGenericEntity( - Collection items) + Collection items) { return new GenericEntity>(items) {} ; @@ -396,7 +394,4 @@ public class UserResource extends AbstractManagerResource /** Field description */ private EncryptionHandler encryptionHandler; - - /** Field description */ - private Provider securityContextProvider; } 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 272803d0b2..a458138910 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/AdminSecurityFilter.java @@ -36,11 +36,12 @@ package sonia.scm.filter; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; -import sonia.scm.util.SecurityUtil; -import sonia.scm.web.security.WebSecurityContext; +import org.apache.shiro.subject.Subject; + +import sonia.scm.config.ScmConfiguration; +import sonia.scm.security.Role; /** * @@ -54,13 +55,12 @@ public class AdminSecurityFilter extends SecurityFilter * Constructs ... * * - * @param securityContextProvider + * @param configuration */ @Inject - public AdminSecurityFilter( - Provider securityContextProvider) + public AdminSecurityFilter(ScmConfiguration configuration) { - super(securityContextProvider); + super(configuration); } //~--- get methods ---------------------------------------------------------- @@ -71,11 +71,13 @@ public class AdminSecurityFilter extends SecurityFilter * * @param securityContext * + * @param subject + * * @return */ @Override - protected boolean hasPermission(WebSecurityContext securityContext) + protected boolean hasPermission(Subject subject) { - return SecurityUtil.isAdmin(securityContext); + return subject.hasRole(Role.ADMIN); } } 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 a276e5920e..ea3deec0e0 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java @@ -36,12 +36,16 @@ package sonia.scm.filter; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; 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; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -69,12 +73,12 @@ public class SecurityFilter extends HttpFilter * Constructs ... * * - * @param securityContextProvider + * @param configuration */ @Inject - public SecurityFilter(Provider securityContextProvider) + public SecurityFilter(ScmConfiguration configuration) { - this.securityContextProvider = securityContextProvider; + this.configuration = configuration; } //~--- methods -------------------------------------------------------------- @@ -92,40 +96,37 @@ public class SecurityFilter extends HttpFilter */ @Override protected void doFilter(HttpServletRequest request, - HttpServletResponse response, FilterChain chain) - throws IOException, ServletException + HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { - WebSecurityContext securityContext = securityContextProvider.get(); + Subject subject = SecurityUtils.getSubject(); - if (securityContext != null) + String uri = + request.getRequestURI().substring(request.getContextPath().length()); + + if (!uri.startsWith(URL_AUTHENTICATION)) { - String uri = - request.getRequestURI().substring(request.getContextPath().length()); - - if (!uri.startsWith(URL_AUTHENTICATION)) + if (hasPermission(subject)) { - if (hasPermission(securityContext)) - { - chain.doFilter(new SecurityHttpServletRequestWrapper(request, - securityContext.getUser()), response); - } - else if (securityContext.isAuthenticated()) - { - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - else - { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED); - } + chain.doFilter(new SecurityHttpServletRequestWrapper(request, + getUser(subject)), response); + } + else if (subject.isAuthenticated()) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + else if (configuration.isAnonymousAccessEnabled()) + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); } else { - chain.doFilter(request, response); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); } } else { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + chain.doFilter(request, response); } } @@ -135,17 +136,42 @@ public class SecurityFilter extends HttpFilter * Method description * * - * @param securityContext + * @param subject * * @return */ - protected boolean hasPermission(WebSecurityContext securityContext) + protected boolean hasPermission(Subject subject) { - return securityContext.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 = SCMContext.ANONYMOUS; + } + + return user; } //~--- fields --------------------------------------------------------------- /** Field description */ - private Provider securityContextProvider; + private ScmConfiguration configuration; } diff --git a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java index 50e0fe90a9..9cf0f39af2 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java +++ b/scm-webapp/src/main/java/sonia/scm/group/DefaultGroupManager.java @@ -47,7 +47,6 @@ import sonia.scm.SCMContextProvider; import sonia.scm.TransformFilter; import sonia.scm.search.SearchRequest; import sonia.scm.search.SearchUtil; -import sonia.scm.security.SecurityContext; import sonia.scm.util.CollectionAppender; import sonia.scm.util.SecurityUtil; import sonia.scm.util.Util; @@ -87,11 +86,9 @@ public class DefaultGroupManager extends AbstractGroupManager * @param groupListenerProvider */ @Inject - public DefaultGroupManager(Provider securityContextProvider, - GroupDAO groupDAO, + public DefaultGroupManager(GroupDAO groupDAO, Provider> groupListenerProvider) { - this.securityContextProvider = securityContextProvider; this.groupDAO = groupDAO; this.groupListenerProvider = groupListenerProvider; } @@ -136,7 +133,7 @@ public class DefaultGroupManager extends AbstractGroupManager group.getType()); } - SecurityUtil.assertIsAdmin(securityContextProvider); + SecurityUtil.assertIsAdmin(); if (groupDAO.contains(group.getName())) { @@ -167,7 +164,7 @@ public class DefaultGroupManager extends AbstractGroupManager group.getType()); } - SecurityUtil.assertIsAdmin(securityContextProvider); + SecurityUtil.assertIsAdmin(); String name = group.getName(); @@ -218,7 +215,7 @@ public class DefaultGroupManager extends AbstractGroupManager group.getType()); } - SecurityUtil.assertIsAdmin(securityContextProvider); + SecurityUtil.assertIsAdmin(); String name = group.getName(); @@ -253,7 +250,7 @@ public class DefaultGroupManager extends AbstractGroupManager group.getType()); } - SecurityUtil.assertIsAdmin(securityContextProvider); + SecurityUtil.assertIsAdmin(); Group fresh = groupDAO.get(group.getName()); @@ -346,7 +343,7 @@ public class DefaultGroupManager extends AbstractGroupManager @Override public Collection getAll(Comparator comparator) { - SecurityUtil.assertIsAdmin(securityContextProvider); + SecurityUtil.assertIsAdmin(); List groups = new ArrayList(); @@ -378,7 +375,7 @@ public class DefaultGroupManager extends AbstractGroupManager public Collection getAll(Comparator comparator, int start, int limit) { - SecurityUtil.assertIsAdmin(securityContextProvider); + SecurityUtil.assertIsAdmin(); return Util.createSubCollection(groupDAO.getAll(), comparator, new CollectionAppender() @@ -449,7 +446,4 @@ public class DefaultGroupManager extends AbstractGroupManager /** Field description */ private Provider> groupListenerProvider; - - /** Field description */ - private Provider securityContextProvider; } diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java index 35ee52c115..5a2d2442d1 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/DefaultPluginManager.java @@ -53,7 +53,6 @@ import sonia.scm.cache.CacheManager; import sonia.scm.config.ScmConfiguration; import sonia.scm.io.ZipUnArchiver; import sonia.scm.net.HttpClient; -import sonia.scm.security.SecurityContext; import sonia.scm.util.AssertUtil; import sonia.scm.util.IOUtil; import sonia.scm.util.SecurityUtil; @@ -123,12 +122,10 @@ public class DefaultPluginManager */ @Inject public DefaultPluginManager(SCMContextProvider context, - Provider securityContextProvicer, ScmConfiguration configuration, PluginLoader pluginLoader, CacheManager cacheManager, Provider clientProvider) { this.context = context; - this.securityContextProvicer = securityContextProvicer; this.configuration = configuration; this.cache = cacheManager.getCache(String.class, PluginCenter.class, CACHE_NAME); @@ -196,7 +193,7 @@ public class DefaultPluginManager @Override public void install(String id) { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); PluginCenter center = getPluginCenter(); @@ -230,7 +227,7 @@ public class DefaultPluginManager @Override public void installPackage(InputStream packageStream) throws IOException { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); File tempDirectory = Files.createTempDir(); @@ -276,7 +273,7 @@ public class DefaultPluginManager @Override public void uninstall(String id) { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); Plugin plugin = installedPlugins.get(id); @@ -320,7 +317,7 @@ public class DefaultPluginManager @Override public void update(String id) { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); String[] idParts = id.split(":"); String groupId = idParts[0]; @@ -364,7 +361,7 @@ public class DefaultPluginManager @Override public PluginInformation get(String id) { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); PluginInformation result = null; @@ -393,7 +390,7 @@ public class DefaultPluginManager public Set get(PluginFilter filter) { AssertUtil.assertIsNotNull(filter); - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); Set infoSet = new HashSet(); @@ -412,7 +409,7 @@ public class DefaultPluginManager @Override public Collection getAll() { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); Set infoSet = getInstalled(); @@ -430,7 +427,7 @@ public class DefaultPluginManager @Override public Collection getAvailable() { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); Set availablePlugins = new HashSet(); Set centerPlugins = getPluginCenter().getPlugins(); @@ -455,7 +452,7 @@ public class DefaultPluginManager @Override public Set getAvailableUpdates() { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); return get(FILTER_UPDATES); } @@ -469,7 +466,7 @@ public class DefaultPluginManager @Override public Set getInstalled() { - SecurityUtil.assertIsAdmin(securityContextProvicer); + SecurityUtil.assertIsAdmin(); Set infoSet = new LinkedHashSet(); @@ -765,9 +762,6 @@ public class DefaultPluginManager /** Field description */ private AetherPluginHandler pluginHandler; - /** Field description */ - private Provider securityContextProvicer; - /** Field description */ private Unmarshaller unmarshaller; } 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 ffa9123050..54b015067c 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/DefaultRepositoryManager.java @@ -36,10 +36,15 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Strings; +import com.google.common.collect.Lists; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.concurrent.SubjectAwareExecutorService; +import org.apache.shiro.subject.Subject; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,15 +60,12 @@ import sonia.scm.util.AssertUtil; import sonia.scm.util.CollectionAppender; import sonia.scm.util.HttpUtil; import sonia.scm.util.IOUtil; -import sonia.scm.util.SecurityUtil; import sonia.scm.util.Util; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -94,10 +96,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager /** * Constructs ... * - * - * - * - * * @param configuration * @param contextProvider * @param keyGenerator @@ -110,18 +108,18 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager @Inject public DefaultRepositoryManager(ScmConfiguration configuration, SCMContextProvider contextProvider, KeyGenerator keyGenerator, - Provider securityContextProvider, RepositoryDAO repositoryDAO, Set handlerSet, Provider> repositoryListenersProvider, Provider> repositoryHooksProvider) { this.configuration = configuration; - this.securityContextProvider = securityContextProvider; this.keyGenerator = keyGenerator; this.repositoryDAO = repositoryDAO; this.repositoryListenersProvider = repositoryListenersProvider; this.repositoryHooksProvider = repositoryHooksProvider; - this.executorService = Executors.newCachedThreadPool(); + + this.executorService = + new SubjectAwareExecutorService(Executors.newCachedThreadPool()); handlerMap = new HashMap(); types = new HashSet(); @@ -169,7 +167,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager repository.getType()); } - SecurityUtil.assertIsAdmin(securityContextProvider); + assertIsAdmin(); AssertUtil.assertIsValid(repository); if (repositoryDAO.contains(repository)) @@ -475,7 +473,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager @Override public Collection getAll(Comparator comparator) { - List repositories = new ArrayList(); + List repositories = Lists.newArrayList(); for (Repository repository : repositoryDAO.getAll()) { @@ -603,7 +601,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager @Override public Collection getConfiguredTypes() { - List validTypes = new ArrayList(); + List validTypes = Lists.newArrayList(); for (RepositoryHandler handler : handlerMap.values()) { @@ -867,25 +865,44 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager /** * Method description * + */ + private void assertIsAdmin() + { + if (!SecurityUtils.getSubject().hasRole("admin")) + { + throw new ScmSecurityException("admin role is required"); + } + } + + /** + * TODO use {@link Subject#checkPermission(org.apache.shiro.authz.Permission)} + * in version 2.x. + * * * @param repository */ private void assertIsOwner(Repository repository) { - PermissionUtil.assertPermission(repository, securityContextProvider, - PermissionType.OWNER); + if (!isPermitted(repository, PermissionType.OWNER)) + { + throw new ScmSecurityException( + "owner permission is required, access denied"); + } } /** - * Method description - * + * TODO use {@link Subject#checkPermission(org.apache.shiro.authz.Permission)} + * in version 2.x. * * @param repository */ private void assertIsReader(Repository repository) { - PermissionUtil.assertPermission(repository, securityContextProvider, - PermissionType.READ); + if (!isReader(repository)) + { + throw new ScmSecurityException( + "reader permission is required, access denied"); + } } //~--- get methods ---------------------------------------------------------- @@ -944,6 +961,20 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager return result; } + /** + * Method description + * + * + * @param repository + * @param type + * + * @return + */ + private boolean isPermitted(Repository repository, PermissionType type) + { + return PermissionUtil.hasPermission(configuration, repository, type); + } + /** * Method description * @@ -954,8 +985,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager */ private boolean isReader(Repository repository) { - return PermissionUtil.hasPermission(repository, securityContextProvider, - PermissionType.READ); + return isPermitted(repository, PermissionType.READ); } //~--- fields --------------------------------------------------------------- @@ -981,9 +1011,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager /** Field description */ private Provider> repositoryListenersProvider; - /** Field description */ - private Provider securityContextProvider; - /** Field description */ private Set types; } diff --git a/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java b/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java index 4ecbbf33e0..b06c11b69f 100644 --- a/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java +++ b/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java @@ -37,15 +37,16 @@ package sonia.scm.search; import com.google.common.base.Function; import com.google.common.collect.Collections2; -import com.google.inject.Provider; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.cache.Cache; -import sonia.scm.util.SecurityUtil; +import sonia.scm.security.ScmSecurityException; import sonia.scm.util.Util; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -77,11 +78,10 @@ public class SearchHandler * @param cache * @param searchable */ - public SearchHandler(Provider securityContextProvider, - Cache cache, - Searchable searchable) + public SearchHandler(Cache cache, + Searchable searchable) { - this.securityContextProvider = securityContextProvider; + this.cache = cache; this.searchable = searchable; } @@ -107,9 +107,14 @@ public class SearchHandler * @return */ public SearchResults search(String queryString, - Function function) + Function function) { - SecurityUtil.assertIsNotAnonymous(securityContextProvider); + Subject subject = SecurityUtils.getSubject(); + + if (!subject.isAuthenticated()) + { + throw new ScmSecurityException("Authentication is required"); + } if (Util.isEmpty(queryString)) { @@ -202,9 +207,6 @@ public class SearchHandler /** Field description */ protected Searchable searchable; - /** Field description */ - protected Provider securityContextProvider; - /** Field description */ private int maxResults = 5; diff --git a/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionResolver.java b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionResolver.java new file mode 100644 index 0000000000..4d4417aaa1 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/RepositoryPermissionResolver.java @@ -0,0 +1,149 @@ +/** + * 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.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Splitter; + +import org.apache.shiro.authz.permission.PermissionResolver; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.repository.PermissionType; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Iterator; +import java.util.Locale; + +/** + * + * @author Sebastian Sdorra + */ +public class RepositoryPermissionResolver implements PermissionResolver +{ + + /** Field description */ + private static final String TYPE_REPOSITORY = "repository"; + + /** + * the logger for RepositoryPermissionResolver + */ + private static final Logger logger = + LoggerFactory.getLogger(RepositoryPermissionResolver.class); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param permissionString + * + * @return + */ + @Override + public RepositoryPermission resolvePermission(String permissionString) + { + RepositoryPermission permission = null; + Iterator permissionIt = + Splitter.on(':').omitEmptyStrings().trimResults().split( + permissionString).iterator(); + + if (permissionIt.hasNext()) + { + String type = permissionIt.next(); + + if (type.equals(TYPE_REPOSITORY)) + { + permission = createRepositoryPermission(permissionIt); + } + else if (logger.isWarnEnabled()) + { + logger.warn("permission '{}' is not a repository permission", + permissionString); + } + } + + return permission; + } + + /** + * Method description + * + * + * @param permissionIt + * + * @return + */ + private RepositoryPermission createRepositoryPermission( + Iterator permissionIt) + { + RepositoryPermission permission = null; + + if (permissionIt.hasNext()) + { + String repositoryId = permissionIt.next(); + + if (permissionIt.hasNext()) + { + try + { + String typeString = permissionIt.next(); + + typeString = typeString.trim().toUpperCase(Locale.ENGLISH); + + PermissionType type = PermissionType.valueOf(typeString); + + permission = new RepositoryPermission(repositoryId, type); + } + catch (IllegalArgumentException ex) + { + logger.warn("type is not a valid permission type", ex); + } + } + else if (logger.isWarnEnabled()) + { + logger.warn("permission type is missing"); + } + } + else if (logger.isWarnEnabled()) + { + logger.warn("repository id and permission type is missing"); + } + + return permission; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java new file mode 100644 index 0000000000..80e9b694b3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/ScmRealm.java @@ -0,0 +1,777 @@ +/** + * 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.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +import org.apache.shiro.authc.AccountException; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.DisabledAccountException; +import org.apache.shiro.authc.SimpleAuthenticationInfo; +import org.apache.shiro.authc.UnknownAccountException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.authc.pam.UnsupportedTokenException; +import org.apache.shiro.authz.AuthorizationInfo; +import org.apache.shiro.authz.SimpleAuthorizationInfo; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.HandlerEvent; +import sonia.scm.cache.Cache; +import sonia.scm.cache.CacheManager; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; +import sonia.scm.group.GroupNames; +import sonia.scm.repository.Permission; +import sonia.scm.repository.PermissionType; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryDAO; +import sonia.scm.repository.RepositoryListener; +import sonia.scm.repository.RepositoryManager; +import sonia.scm.user.User; +import sonia.scm.user.UserDAO; +import sonia.scm.user.UserEventHack; +import sonia.scm.user.UserException; +import sonia.scm.user.UserListener; +import sonia.scm.user.UserManager; +import sonia.scm.util.Util; +import sonia.scm.web.security.AuthenticationManager; +import sonia.scm.web.security.AuthenticationResult; +import sonia.scm.web.security.AuthenticationState; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +@Singleton +public class ScmRealm extends AuthorizingRealm + implements RepositoryListener, UserListener +{ + + /** Field description */ + public static final String NAME = "scm"; + + /** Field description */ + private static final String CACHE_NAME = "sonia.cache.authorizing"; + + /** Field description */ + private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; + + /** + * the logger for ScmRealm + */ + private static final Logger logger = LoggerFactory.getLogger(ScmRealm.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * + * @param configuration + * @param cacheManager + * @param userManager + * @param groupManager + * @param repositoryManager + * @param repositoryDAO + * @param userDAO + * @param authenticator + * @param requestProvider + * @param responseProvider + */ + @Inject + public ScmRealm(ScmConfiguration configuration, CacheManager cacheManager, + UserManager userManager, GroupManager groupManager, + RepositoryManager repositoryManager, RepositoryDAO repositoryDAO, + UserDAO userDAO, AuthenticationManager authenticator, + Provider requestProvider, + Provider responseProvider) + { + this.configuration = configuration; + this.userManager = userManager; + this.groupManager = groupManager; + this.repositoryDAO = repositoryDAO; + this.userDAO = userDAO; + this.authenticator = authenticator; + this.requestProvider = requestProvider; + this.responseProvider = responseProvider; + + // init cache + this.cache = cacheManager.getCache(String.class, AuthorizationInfo.class, + CACHE_NAME); + + // set token class + setAuthenticationTokenClass(UsernamePasswordToken.class); + + // use own custom caching + setCachingEnabled(false); + setAuthenticationCachingEnabled(false); + setAuthorizationCachingEnabled(false); + + // set components + setPermissionResolver(new RepositoryPermissionResolver()); + + // add listeners for caching + userManager.addListener(this); + repositoryManager.addListener(this); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param repository + * @param event + */ + @Override + public void onEvent(Repository repository, HandlerEvent event) + { + if (event.isPost()) + { + + if (logger.isDebugEnabled()) + { + logger.debug("clear cache, because repository {} has changed", + repository.getName()); + } + + cache.clear(); + } + } + + /** + * Method description + * + * + * @param user + * @param event + */ + @Override + public void onEvent(User user, HandlerEvent event) + { + if (event.isPost()) + { + if (logger.isDebugEnabled()) + { + logger.debug( + "clear cache of user {}, because user properties have changed", + user.getName()); + } + + cache.remove(user.getId()); + } + } + + /** + * Method description + * + * + * @param token + * + * @param authToken + * + * @return + * + * @throws AuthenticationException + */ + @Override + protected AuthenticationInfo doGetAuthenticationInfo( + AuthenticationToken authToken) + throws AuthenticationException + { + if (!(authToken instanceof UsernamePasswordToken)) + { + throw new UnsupportedTokenException("ScmAuthenticationToken is required"); + } + + UsernamePasswordToken token = (UsernamePasswordToken) authToken; + + AuthenticationInfo info = null; + AuthenticationResult result = + authenticator.authenticate(requestProvider.get(), responseProvider.get(), + token.getUsername(), new String(token.getPassword())); + + if ((result != null) && (AuthenticationState.SUCCESS == result.getState())) + { + info = createAuthenticationInfo(token, result); + } + else if ((result != null) + && (AuthenticationState.NOT_FOUND == result.getState())) + { + throw new UnknownAccountException( + "unknown account ".concat(token.getUsername())); + } + else + { + throw new AccountException("authentication failed"); + } + + return info; + } + + /** + * Method description + * + * + * @param principals + * + * @return + */ + @Override + protected AuthorizationInfo doGetAuthorizationInfo( + PrincipalCollection principals) + { + User user = principals.oneByType(User.class); + + AuthorizationInfo info = cache.get(user.getId()); + + if (info == null) + { + if (logger.isTraceEnabled()) + { + logger.trace("collect AuthorizationInfo for user {}", user.getName()); + } + + GroupNames groups = principals.oneByType(GroupNames.class); + + info = createAuthorizationInfo(user, groups); + cache.put(user.getId(), info); + } + else if (logger.isTraceEnabled()) + { + logger.trace("retrieve AuthorizationInfo for user {} from cache", + user.getName()); + } + + return info; + } + + /** + * Method description + * + * + * @param request + * @param password + * @param ar + * + * @return + */ + private Set authenticate(HttpServletRequest request, String password, + AuthenticationResult ar) + { + Set groupSet = null; + User user = ar.getUser(); + + try + { + groupSet = createGroupSet(ar); + + // check for admin user + checkForAuthenticatedAdmin(user, groupSet); + + // store user + User dbUser = userDAO.get(user.getName()); + + if (dbUser != null) + { + checkDBForAdmin(user, dbUser); + checkDBForActive(user, dbUser); + } + + // create new user + else if (user.isValid()) + { + user.setCreationDate(System.currentTimeMillis()); + + // TODO find a better way + UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_CREATE); + userDAO.add(user); + UserEventHack.fireEvent(userManager, user, HandlerEvent.CREATE); + } + else if (logger.isErrorEnabled()) + { + logger.error("could not create user {}, beacause it is not valid", + user.getName()); + } + + if (user.isActive()) + { + + if (logger.isDebugEnabled()) + { + logGroups(user, groupSet); + } + + // store encrypted credentials in session + String credentials = user.getName(); + + if (Util.isNotEmpty(password)) + { + credentials = credentials.concat(":").concat(password); + } + + credentials = CipherUtil.getInstance().encode(credentials); + request.getSession(true).setAttribute(SCM_CREDENTIALS, credentials); + } + else + { + + String msg = "user ".concat(user.getName()).concat(" is deactivated"); + + if (logger.isWarnEnabled()) + { + logger.warn(msg); + } + + throw new DisabledAccountException(msg); + + } + } + catch (Exception ex) + { + logger.error("authentication failed", ex); + + throw new AuthenticationException("authentication failed", ex); + } + + return groupSet; + } + + /** + * Method description + * + * + * @param user + * @param dbUser + */ + private void checkDBForActive(User user, User dbUser) + { + + // user is deactivated by database + if (!dbUser.isActive()) + { + if (logger.isDebugEnabled()) + { + logger.debug("user {} is marked as deactivated by local database", + user.getName()); + } + + user.setActive(false); + } + } + + /** + * Method description + * + * + * @param user + * @param dbUser + * + * @throws IOException + * @throws UserException + */ + private void checkDBForAdmin(User user, User dbUser) + throws UserException, IOException + { + + // if database user is an admin, set admin for the current user + if (dbUser.isAdmin()) + { + if (logger.isDebugEnabled()) + { + logger.debug("user {} of type {} is marked as admin by local database", + user.getName(), user.getType()); + } + + user.setAdmin(true); + } + + // modify existing user, copy properties except password and admin + if (user.copyProperties(dbUser, false)) + { + userManager.modify(dbUser); + } + } + + /** + * Method description + * + * + * @param user + * @param groupSet + */ + private void checkForAuthenticatedAdmin(User user, Set groupSet) + { + if (!user.isAdmin()) + { + user.setAdmin(isAdmin(user, groupSet)); + + if (logger.isDebugEnabled() && user.isAdmin()) + { + logger.debug("user {} is marked as admin by configuration", + user.getName()); + } + } + else if (logger.isDebugEnabled()) + { + logger.debug("authenticator {} marked user {} as admin", user.getType(), + user.getName()); + } + } + + /** + * Method description + * + * + * @param user + * @param groups + * + * @return + */ + private List collectRepositoryPermissions( + User user, GroupNames groups) + { + List permissions = Lists.newArrayList(); + + for (Repository repository : repositoryDAO.getAll()) + { + if (logger.isTraceEnabled()) + { + logger.trace("collect permissions for repository {} and user {}", + repository.getName(), user.getName()); + } + + collectRepositoryPermissions(permissions, repository, user, groups); + } + + return permissions; + } + + /** + * Method description + * + * + * @param permissions + * @param repository + * @param user + * @param groups + */ + private void collectRepositoryPermissions( + List permissions, Repository repository, + User user, GroupNames groups) + { + List repositoryPermissions = repository.getPermissions(); + + if (Util.isNotEmpty(repositoryPermissions)) + { + + for (Permission permission : repositoryPermissions) + { + if (isUserPermission(user, groups, permission)) + { + RepositoryPermission rp = new RepositoryPermission(repository, + permission.getType()); + + if (logger.isTraceEnabled()) + { + logger.trace("add repository permission {} for user {}", rp, + user.getName()); + } + + permissions.add(rp); + } + } + } + else if (logger.isTraceEnabled()) + { + logger.trace("repository {} has not permission entries", + repository.getName()); + } + } + + /** + * Method description + * + * + * @param token + * @param result + * + * @return + */ + private AuthenticationInfo createAuthenticationInfo( + UsernamePasswordToken token, AuthenticationResult result) + { + User user = result.getUser(); + Collection groups = authenticate(requestProvider.get(), + new String(token.getPassword()), result); + + SimplePrincipalCollection collection = new SimplePrincipalCollection(); + + /* + * the first (primary) principal should be a unique identifier + */ + collection.add(user.getId(), NAME); + collection.add(user, NAME); + collection.add(new GroupNames(groups), NAME); + + return new SimpleAuthenticationInfo(collection, token.getPassword()); + } + + /** + * Method description + * + * + * @param user + * @param groups + * + * @return + */ + private AuthorizationInfo createAuthorizationInfo(User user, + GroupNames groups) + { + Set roles = Sets.newHashSet(); + List permissions = null; + + roles.add(Role.USER); + + if (user.isAdmin()) + { + if (logger.isDebugEnabled()) + { + logger.debug("grant admin role for user {}", user.getName()); + } + + roles.add(Role.ADMIN); + permissions = Lists.newArrayList(); + permissions.add(new RepositoryPermission(RepositoryPermission.WILDCARD, + PermissionType.OWNER)); + } + else + { + permissions = collectRepositoryPermissions(user, groups); + } + + SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles); + + info.addObjectPermissions(permissions); + + return info; + } + + /** + * Method description + * + * + * @param ar + * + * @return + */ + private Set createGroupSet(AuthenticationResult ar) + { + Set groupSet = Sets.newHashSet(); + + // load external groups + Collection extGroups = ar.getGroups(); + + if (extGroups != null) + { + groupSet.addAll(extGroups); + } + + // load internal groups + loadGroups(ar.getUser(), groupSet); + + return groupSet; + } + + /** + * Method description + * + * + * + * @param user + * @param groupSet + */ + private void loadGroups(User user, Set groupSet) + { + Collection groupCollection = + groupManager.getGroupsForMember(user.getName()); + + if (groupCollection != null) + { + for (Group group : groupCollection) + { + groupSet.add(group.getName()); + } + } + } + + /** + * Method description + * + * + * @param user + * @param groups + */ + private void logGroups(User user, Set groups) + { + StringBuilder msg = new StringBuilder("user "); + + msg.append(user.getName()); + + if (Util.isNotEmpty(groups)) + { + msg.append(" is member of "); + + Joiner.on(", ").appendTo(msg, groups); + } + else + { + msg.append(" is not a member of a group"); + } + + logger.debug(msg.toString()); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * + * + * @param user + * @param groups + * @return + */ + private boolean isAdmin(User user, Collection groups) + { + boolean result = false; + Set adminUsers = configuration.getAdminUsers(); + + if (adminUsers != null) + { + result = adminUsers.contains(user.getName()); + } + + if (!result) + { + Set adminGroups = configuration.getAdminGroups(); + + if (adminGroups != null) + { + result = Util.containsOne(adminGroups, groups); + } + } + + return result; + } + + /** + * Method description + * + * + * @param user + * @param groups + * @param perm + * + * @return + */ + private boolean isUserPermission(User user, GroupNames groups, + Permission perm) + { + //J- + return (perm.isGroupPermission() && groups.contains(perm.getName())) + || ((!perm.isGroupPermission()) && user.getName().equals(perm.getName())); + //J+ + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private AuthenticationManager authenticator; + + /** Field description */ + private Cache cache; + + /** Field description */ + private ScmConfiguration configuration; + + /** Field description */ + private GroupManager groupManager; + + /** Field description */ + private RepositoryDAO repositoryDAO; + + /** Field description */ + private Provider requestProvider; + + /** Field description */ + private Provider responseProvider; + + /** Field description */ + private UserDAO userDAO; + + /** Field description */ + private UserManager userManager; +} diff --git a/scm-webapp/src/main/java/sonia/scm/template/MustacheTemplateNotFoundException.java b/scm-webapp/src/main/java/sonia/scm/template/MustacheTemplateNotFoundException.java index 0f9038d73b..f9c0e59e00 100644 --- a/scm-webapp/src/main/java/sonia/scm/template/MustacheTemplateNotFoundException.java +++ b/scm-webapp/src/main/java/sonia/scm/template/MustacheTemplateNotFoundException.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; /** diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 2b5a89357d..807da0cf62 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -39,6 +39,9 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,13 +50,13 @@ import sonia.scm.SCMContextProvider; import sonia.scm.TransformFilter; import sonia.scm.search.SearchRequest; import sonia.scm.search.SearchUtil; +import sonia.scm.security.Role; import sonia.scm.security.ScmSecurityException; import sonia.scm.util.AssertUtil; import sonia.scm.util.CollectionAppender; import sonia.scm.util.IOUtil; import sonia.scm.util.SecurityUtil; import sonia.scm.util.Util; -import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ @@ -104,11 +107,9 @@ public class DefaultUserManager extends AbstractUserManager * @param userListenerProvider */ @Inject - public DefaultUserManager( - Provider scurityContextProvider, UserDAO userDAO, - Provider> userListenerProvider) + public DefaultUserManager(UserDAO userDAO, + Provider> userListenerProvider) { - this.scurityContextProvider = scurityContextProvider; this.userDAO = userDAO; this.userListenerProvider = userListenerProvider; } @@ -166,9 +167,14 @@ public class DefaultUserManager extends AbstractUserManager logger.info("create user {} of type {}", user.getName(), user.getType()); } - User currentUser = SecurityUtil.getCurrentUser(scurityContextProvider); + Subject subject = SecurityUtils.getSubject(); - if (!user.equals(currentUser) &&!currentUser.isAdmin()) + if (!subject.isAuthenticated()) + { + throw new ScmSecurityException("user is not authenticated"); + } + + if (!subject.hasRole(Role.ADMIN)) { throw new ScmSecurityException("admin account is required"); } @@ -202,7 +208,7 @@ public class DefaultUserManager extends AbstractUserManager logger.info("delete user {} of type {}", user.getName(), user.getType()); } - SecurityUtil.assertIsAdmin(scurityContextProvider); + SecurityUtil.assertIsAdmin(); String name = user.getName(); @@ -259,9 +265,17 @@ public class DefaultUserManager extends AbstractUserManager logger.info("modify user {} of type {}", user.getName(), user.getType()); } - User currentUser = SecurityUtil.getCurrentUser(scurityContextProvider); + Subject subject = SecurityUtils.getSubject(); - if (!user.getName().equals(currentUser.getName()) &&!currentUser.isAdmin()) + if (!subject.isAuthenticated()) + { + throw new ScmSecurityException("user is not authenticated"); + } + + User currentUser = subject.getPrincipals().oneByType(User.class); + + if (!user.getName().equals(currentUser.getName()) + &&!subject.hasRole(Role.ADMIN)) { throw new ScmSecurityException("admin account is required"); } @@ -299,7 +313,7 @@ public class DefaultUserManager extends AbstractUserManager logger.info("refresh user {} of type {}", user.getName(), user.getType()); } - SecurityUtil.assertIsAdmin(scurityContextProvider); + SecurityUtil.assertIsAdmin(); User fresh = userDAO.get(user.getName()); @@ -328,7 +342,7 @@ public class DefaultUserManager extends AbstractUserManager } return SearchUtil.search(searchRequest, userDAO.getAll(), - new TransformFilter() + new TransformFilter() { @Override public User accept(User user) @@ -336,7 +350,7 @@ public class DefaultUserManager extends AbstractUserManager User result = null; if (SearchUtil.matchesOne(searchRequest, user.getName(), - user.getDisplayName(), user.getMail())) + user.getDisplayName(), user.getMail())) { result = user.clone(); } @@ -394,7 +408,7 @@ public class DefaultUserManager extends AbstractUserManager @Override public Collection getAll(Comparator comparator) { - SecurityUtil.assertIsAdmin(scurityContextProvider); + SecurityUtil.assertIsAdmin(); List users = new ArrayList(); @@ -424,12 +438,12 @@ public class DefaultUserManager extends AbstractUserManager */ @Override public Collection getAll(Comparator comaparator, int start, - int limit) + int limit) { - SecurityUtil.assertIsAdmin(scurityContextProvider); + SecurityUtil.assertIsAdmin(); return Util.createSubCollection(userDAO.getAll(), comaparator, - new CollectionAppender() + new CollectionAppender() { @Override public void append(Collection collection, User item) @@ -525,15 +539,12 @@ public class DefaultUserManager extends AbstractUserManager } catch (JAXBException ex) { - logger.error(ex.getMessage(), ex); + logger.error("could not create default accounts", ex); } } //~--- fields --------------------------------------------------------------- - /** Field description */ - private Provider scurityContextProvider; - /** Field description */ private UserDAO userDAO; diff --git a/scm-webapp/src/main/java/sonia/scm/user/UserEventHack.java b/scm-webapp/src/main/java/sonia/scm/user/UserEventHack.java new file mode 100644 index 0000000000..13d5f1f42b --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/user/UserEventHack.java @@ -0,0 +1,76 @@ +/** + * 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.user; + +//~--- non-JDK imports -------------------------------------------------------- + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.HandlerEvent; + +/** + * + * @author Sebastian Sdorra + */ +public class UserEventHack +{ + + /** + * the logger for UserEventHack + */ + private static final Logger logger = + LoggerFactory.getLogger(UserEventHack.class); + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param userManager + * @param user + * @param event + */ + public static void fireEvent(UserManager userManager, User user, + HandlerEvent event) + { + if (userManager instanceof AbstractUserManager) + { + ((AbstractUserManager) userManager).fireEvent(user, event); + } + else if (logger.isWarnEnabled()) + { + logger.warn("user manager is not an instance of AbstractUserManager"); + } + } +} 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 c0e9718ae7..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 @@ -36,9 +36,9 @@ package sonia.scm.web.security; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import com.google.inject.Provider; import com.google.inject.Singleton; +import sonia.scm.config.ScmConfiguration; import sonia.scm.web.filter.BasicAuthenticationFilter; //~--- JDK imports ------------------------------------------------------------ @@ -73,13 +73,12 @@ public class ApiBasicAuthenticationFilter extends BasicAuthenticationFilter * Constructs ... * * - * @param securityContextProvider + * @param configuration */ @Inject - public ApiBasicAuthenticationFilter( - Provider securityContextProvider) + public ApiBasicAuthenticationFilter(ScmConfiguration configuration) { - super(securityContextProvider); + super(configuration); } //~--- methods -------------------------------------------------------------- @@ -97,14 +96,14 @@ public class ApiBasicAuthenticationFilter extends BasicAuthenticationFilter */ @Override protected void doFilter(HttpServletRequest request, - HttpServletResponse response, FilterChain chain) - throws IOException, ServletException + HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { // skip filter on authentication resource if (request.getRequestURI().contains(URI_LOGIN) - || request.getRequestURI().contains(URI_STATE) - || request.getRequestURI().contains(URI_LOGOUT)) + || request.getRequestURI().contains(URI_STATE) + || request.getRequestURI().contains(URI_LOGOUT)) { chain.doFilter(request, response); } @@ -127,9 +126,8 @@ public class ApiBasicAuthenticationFilter extends BasicAuthenticationFilter */ @Override protected void handleUnauthorized(HttpServletRequest request, - HttpServletResponse response, - FilterChain chain) - throws IOException, ServletException + HttpServletResponse response, FilterChain chain) + throws IOException, ServletException { chain.doFilter(request, response); } diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/BasicSecurityContext.java b/scm-webapp/src/main/java/sonia/scm/web/security/BasicSecurityContext.java index cf1c78f97c..1cd5d645cd 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/BasicSecurityContext.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/BasicSecurityContext.java @@ -35,30 +35,26 @@ package sonia.scm.web.security; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.collect.Sets; import com.google.inject.Inject; -import com.google.inject.servlet.SessionScoped; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.config.ScmConfiguration; -import sonia.scm.group.Group; -import sonia.scm.group.GroupManager; -import sonia.scm.security.CipherUtil; +import sonia.scm.group.GroupNames; +import sonia.scm.security.Tokens; import sonia.scm.user.User; -import sonia.scm.user.UserException; import sonia.scm.user.UserManager; -import sonia.scm.util.Util; //~--- JDK imports ------------------------------------------------------------ -import java.io.IOException; - import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; +import java.util.Collections; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -68,7 +64,6 @@ import javax.servlet.http.HttpSession; * * @author Sebastian Sdorra */ -@SessionScoped public class BasicSecurityContext implements WebSecurityContext { @@ -88,21 +83,14 @@ public class BasicSecurityContext implements WebSecurityContext * Constructs ... * * - * * @param configuration - * @param authenticator - * @param groupManager * @param userManager */ @Inject public BasicSecurityContext(ScmConfiguration configuration, - AuthenticationManager authenticator, - GroupManager groupManager, - UserManager userManager) + UserManager userManager) { this.configuration = configuration; - this.authenticator = authenticator; - this.groupManager = groupManager; this.userManager = userManager; } @@ -121,22 +109,27 @@ public class BasicSecurityContext implements WebSecurityContext */ @Override public User authenticate(HttpServletRequest request, - HttpServletResponse response, String username, - String password) + HttpServletResponse response, String username, String password) { - if ( logger.isTraceEnabled() ){ - logger.trace("start authentication for user {}", username); - } - AuthenticationResult ar = authenticator.authenticate(request, response, - username, password); + User user = null; - if ( logger.isTraceEnabled() ){ - logger.trace("authentication ends with {}", ar); - } - - if ((ar != null) && (ar.getState() == AuthenticationState.SUCCESS)) + try { - authenticate(request, password, ar); + + Subject subject = SecurityUtils.getSubject(); + + subject.login(Tokens.createAuthenticationToken(request, username, + password)); + + user = subject.getPrincipals().oneByType(User.class); + + } + catch (AuthenticationException ex) + { + if (logger.isWarnEnabled()) + { + logger.warn("authentication failed", ex); + } } return user; @@ -152,8 +145,7 @@ public class BasicSecurityContext implements WebSecurityContext @Override public void logout(HttpServletRequest request, HttpServletResponse response) { - user = null; - groups = new HashSet(); + SecurityUtils.getSubject().logout(); HttpSession session = request.getSession(false); @@ -174,12 +166,20 @@ public class BasicSecurityContext implements WebSecurityContext @Override public Collection getGroups() { - if (groups == null) + GroupNames groups = getPrincipal(GroupNames.class); + + Collection groupCollection = null; + + if (groups != null) { - groups = new HashSet(); + groupCollection = groups.getCollection(); + } + else + { + groupCollection = Collections.EMPTY_LIST; } - return groups; + return groupCollection; } /** @@ -191,6 +191,8 @@ public class BasicSecurityContext implements WebSecurityContext @Override public User getUser() { + User user = getPrincipal(User.class); + if ((user == null) && configuration.isAnonymousAccessEnabled()) { user = userManager.get(USER_ANONYMOUS); @@ -211,276 +213,27 @@ public class BasicSecurityContext implements WebSecurityContext return getUser() != null; } - //~--- methods -------------------------------------------------------------- - /** * Method description * * - * @param request - * @param password - * @param ar - */ - private void authenticate(HttpServletRequest request, String password, - AuthenticationResult ar) - { - user = ar.getUser(); - - try - { - Set groupSet = createGroupSet(ar); - - // check for admin user - checkForAuthenticatedAdmin(user, groupSet); - - // store user - User dbUser = userManager.get(user.getName()); - - if (dbUser != null) - { - checkDBForAdmin(user, dbUser); - checkDBForActive(user, dbUser); - } - - // create new user - else - { - userManager.create(user); - } - - if (user.isActive()) - { - groups = groupSet; - - if (logger.isDebugEnabled()) - { - logGroups(); - } - - // store encrypted credentials in session - String credentials = user.getName(); - - if (Util.isNotEmpty(password)) - { - credentials = credentials.concat(":").concat(password); - } - - credentials = CipherUtil.getInstance().encode(credentials); - request.getSession(true).setAttribute(SCM_CREDENTIALS, credentials); - } - else - { - if (logger.isWarnEnabled()) - { - logger.warn("user {} is deactivated", user.getName()); - } - - user = null; - groups = null; - } - } - catch (Exception ex) - { - user = null; - - if (groups != null) - { - groups.clear(); - } - - logger.error("authentication failed", ex); - } - } - - /** - * Method description - * - * - * @param user - * @param dbUser - */ - private void checkDBForActive(User user, User dbUser) - { - - // user is deactivated by database - if (!dbUser.isActive()) - { - if (logger.isDebugEnabled()) - { - logger.debug("user {} is marked as deactivated by local database", - user.getName()); - } - - user.setActive(false); - } - } - - /** - * Method description - * - * - * @param user - * @param dbUser - * - * @throws IOException - * @throws UserException - */ - private void checkDBForAdmin(User user, User dbUser) - throws UserException, IOException - { - - // if database user is an admin, set admin for the current user - if (dbUser.isAdmin()) - { - if (logger.isDebugEnabled()) - { - logger.debug("user {} of type {} is marked as admin by local database", - user.getName(), user.getType()); - } - - user.setAdmin(true); - } - - // modify existing user, copy properties except password and admin - if (user.copyProperties(dbUser, false)) - { - userManager.modify(dbUser); - } - } - - /** - * Method description - * - * - * @param user - * @param groupSet - */ - private void checkForAuthenticatedAdmin(User user, Set groupSet) - { - if (!user.isAdmin()) - { - user.setAdmin(isAdmin(groupSet)); - - if (logger.isDebugEnabled() && user.isAdmin()) - { - logger.debug("user {} is marked as admin by configuration", - user.getName()); - } - } - else if (logger.isDebugEnabled()) - { - logger.debug("authenticator {} marked user {} as admin", user.getType(), - user.getName()); - } - } - - /** - * Method description - * - * - * @param ar + * @param clazz + * @param * * @return */ - private Set createGroupSet(AuthenticationResult ar) + private T getPrincipal(Class clazz) { - Set groupSet = Sets.newHashSet(); + T result = null; + Subject subject = SecurityUtils.getSubject(); - // load external groups - Collection extGroups = ar.getGroups(); - - if (extGroups != null) + if (subject.isAuthenticated()) { - groupSet.addAll(extGroups); - } + PrincipalCollection pc = subject.getPrincipals(); - // load internal groups - loadGroups(groupSet); - - return groupSet; - } - - /** - * Method description - * - * - * @param groupSet - */ - private void loadGroups(Set groupSet) - { - Collection groupCollection = - groupManager.getGroupsForMember(user.getName()); - - if (groupCollection != null) - { - for (Group group : groupCollection) + if (pc != null) { - groupSet.add(group.getName()); - } - } - } - - /** - * Method description - * - */ - private void logGroups() - { - StringBuilder msg = new StringBuilder("user "); - - msg.append(user.getName()); - - if (Util.isNotEmpty(groups)) - { - msg.append(" is member of "); - - Iterator groupIt = groups.iterator(); - - while (groupIt.hasNext()) - { - msg.append(groupIt.next()); - - if (groupIt.hasNext()) - { - msg.append(", "); - } - } - } - else - { - msg.append(" is not a member of a group"); - } - - logger.debug(msg.toString()); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * - * @param groups - * @return - */ - private boolean isAdmin(Collection groups) - { - boolean result = false; - Set adminUsers = configuration.getAdminUsers(); - - if (adminUsers != null) - { - result = adminUsers.contains(user.getName()); - } - - if (!result) - { - Set adminGroups = configuration.getAdminGroups(); - - if (adminGroups != null) - { - result = Util.containsOne(adminGroups, groups); + result = pc.oneByType(clazz); } } @@ -489,21 +242,9 @@ public class BasicSecurityContext implements WebSecurityContext //~--- fields --------------------------------------------------------------- - /** Field description */ - private AuthenticationManager authenticator; - /** Field description */ private ScmConfiguration configuration; - /** Field description */ - private GroupManager groupManager; - - /** Field description */ - private Set groups = new HashSet(); - - /** Field description */ - private User user; - /** Field description */ private UserManager userManager; } 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 2f51ef6cfe..b2d15959a4 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 @@ -89,9 +89,9 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager */ @Inject public ChainAuthenticatonManager( - Set authenticationHandlerSet, - EncryptionHandler encryptionHandler, CacheManager cacheManager, - Provider> authenticationListenerProvider) + Set authenticationHandlerSet, + EncryptionHandler encryptionHandler, CacheManager cacheManager, + Provider> authenticationListenerProvider) { AssertUtil.assertIsNotEmpty(authenticationHandlerSet); AssertUtil.assertIsNotNull(cacheManager); @@ -99,8 +99,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager this.encryptionHandler = encryptionHandler; this.authenticationListenerProvider = authenticationListenerProvider; this.cache = cacheManager.getCache(String.class, - AuthenticationCacheValue.class, - CACHE_NAME); + AuthenticationCacheValue.class, CACHE_NAME); // addListeners(authenticationListeners); } @@ -120,7 +119,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager */ @Override public AuthenticationResult authenticate(HttpServletRequest request, - HttpServletResponse response, String username, String password) + HttpServletResponse response, String username, String password) { AssertUtil.assertIsNotEmpty(username); AssertUtil.assertIsNotEmpty(password); @@ -133,7 +132,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager if (logger.isTraceEnabled()) { logger.trace("no authentication result for user {} found in cache", - username); + username); } ar = doAuthentication(request, response, username, password); @@ -141,7 +140,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager if ((ar != null) && ar.isCacheable()) { cache.put(username, - new AuthenticationCacheValue(ar, encryptedPassword)); + new AuthenticationCacheValue(ar, encryptedPassword)); } } else if (logger.isDebugEnabled()) @@ -212,7 +211,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager * @return */ private AuthenticationResult doAuthentication(HttpServletRequest request, - HttpServletResponse response, String username, String password) + HttpServletResponse response, String username, String password) { AuthenticationResult ar = null; @@ -226,7 +225,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager if (logger.isTraceEnabled()) { logger.trace("check authenticator {} for user {}", - authenticator.getClass(), username); + authenticator.getClass(), username); } try @@ -237,12 +236,12 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager if (logger.isDebugEnabled()) { logger.debug("authenticator {} ends with result, {}", - authenticator.getClass().getName(), result); + authenticator.getClass().getName(), result); } if ((result != null) && (result.getState() != null) - && (result.getState().isSuccessfully() - || (result.getState() == AuthenticationState.FAILED))) + && (result.getState().isSuccessfully() + || (result.getState() == AuthenticationState.FAILED))) { if (result.getState().isSuccessfully() && (result.getUser() != null)) { @@ -260,7 +259,9 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager } catch (Exception ex) { - logger.error(ex.getMessage(), ex); + logger.error( + "error durring authentication process of ".concat( + authenticator.getClass().getName()), ex); } } @@ -279,7 +280,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager * @return */ private AuthenticationResult getCached(String username, - String encryptedPassword) + String encryptedPassword) { AuthenticationResult result = null; AuthenticationCacheValue value = cache.get(username); @@ -326,7 +327,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager { this.authenticationResult = new AuthenticationResult(ar.getUser().clone(), ar.getGroups(), - ar.getState()); + ar.getState()); this.password = password; } diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java index 31c80b680f..3b9cc11224 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java @@ -30,19 +30,29 @@ */ + package sonia.scm.web.security; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; import com.google.inject.Injector; -import com.google.inject.Provider; import com.google.inject.Singleton; -import com.google.inject.name.Named; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.ThreadContext; +import org.apache.shiro.util.ThreadState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.SCMContext; +import sonia.scm.group.GroupNames; +import sonia.scm.security.ScmRealm; import sonia.scm.user.User; import sonia.scm.util.AssertUtil; @@ -77,15 +87,14 @@ public class DefaultAdministrationContext implements AdministrationContext * @param injector * @param userSessionProvider * @param contextHolder + * @param securityManager */ @Inject public DefaultAdministrationContext(Injector injector, - @Named("userSession") Provider userSessionProvider, - LocalSecurityContextHolder contextHolder) + org.apache.shiro.mgt.SecurityManager securityManager) { this.injector = injector; - this.userSessionProvider = userSessionProvider; - this.contextHolder = contextHolder; + this.securityManager = securityManager; URL url = DefaultAdministrationContext.class.getResource(SYSTEM_ACCOUNT); @@ -94,9 +103,9 @@ public class DefaultAdministrationContext implements AdministrationContext throw new RuntimeException("could not find resource for system account"); } - User user = JAXB.unmarshal(url, User.class); + User adminUser = JAXB.unmarshal(url, User.class); - adminContext = new AdministrationSecurityContext(user); + principalCollection = createAdminCollection(adminUser); } //~--- methods -------------------------------------------------------------- @@ -112,24 +121,15 @@ public class DefaultAdministrationContext implements AdministrationContext { AssertUtil.assertIsNotNull(action); - if (logger.isWarnEnabled()) + if (ThreadContext.getSecurityManager() != null) { - String user = SecurityUtil.getUsername(userSessionProvider); - - logger.warn("user {} executes {} as admin", user, - action.getClass().getName()); + doRunAsInWebSessionContext(action); + } + else + { + doRunAsInNonWebSessionContext(action); } - contextHolder.set(adminContext); - - try - { - action.run(); - } - finally - { - contextHolder.remove(); - } } /** @@ -146,17 +146,136 @@ public class DefaultAdministrationContext implements AdministrationContext runAsAdmin(action); } + /** + * Method description + * + * + * @param adminUser + * + * @return + */ + private PrincipalCollection createAdminCollection(User adminUser) + { + SimplePrincipalCollection collection = new SimplePrincipalCollection(); + + collection.add(adminUser.getId(), ScmRealm.NAME); + collection.add(adminUser, ScmRealm.NAME); + collection.add(new GroupNames(), ScmRealm.NAME); + + return collection; + } + + /** + * Method description + * + * + * @param action + */ + private void doRunAsInNonWebSessionContext(PrivilegedAction action) + { + if (logger.isTraceEnabled()) + { + logger.trace("bind shiro security manager to current thread"); + } + + try + { + SecurityUtils.setSecurityManager(securityManager); + + //J- + Subject subject = new Subject.Builder(securityManager) + .authenticated(true) + .principals(principalCollection) + .buildSubject(); + //J+ + ThreadState state = new SubjectThreadState(subject); + + state.bind(); + + try + { + if (logger.isInfoEnabled()) + { + logger.info("execute action {} in administration context", + action.getClass().getName()); + } + + action.run(); + } + finally + { + state.clear(); + } + } + finally + { + SecurityUtils.setSecurityManager(null); + } + } + + /** + * Method description + * + * + * @param action + */ + private void doRunAsInWebSessionContext(PrivilegedAction action) + { + Subject subject = SecurityUtils.getSubject(); + + String principal = (String) subject.getPrincipal(); + + if (logger.isInfoEnabled()) + { + String username = null; + + if (subject.isAuthenticated()) + { + username = principal; + } + else + { + username = SCMContext.USER_ANONYMOUS; + } + + logger.info("user {} executes {} as admin", username, + action.getClass().getName()); + } + + subject.runAs(principalCollection); + + try + { + action.run(); + } + finally + { + + PrincipalCollection collection = subject.releaseRunAs(); + + if (logger.isDebugEnabled()) + { + logger.debug("release runas for user {}", + collection.getPrimaryPrincipal()); + } + + if (!subject.getPrincipal().equals(principal)) + { + logger.error("release runas failed, {} is not equal with {}, logout.", + subject.getPrincipal(), principal); + subject.logout(); + } + } + } + //~--- fields --------------------------------------------------------------- - /** Field description */ - private AdministrationSecurityContext adminContext; - - /** Field description */ - private LocalSecurityContextHolder contextHolder; - /** Field description */ private Injector injector; /** Field description */ - private Provider userSessionProvider; + private PrincipalCollection principalCollection; + + /** Field description */ + private org.apache.shiro.mgt.SecurityManager securityManager; } diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/SecurityUtil.java b/scm-webapp/src/main/java/sonia/scm/web/security/SecurityUtil.java index 84b735cfa4..e5b7f72e7e 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/SecurityUtil.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/SecurityUtil.java @@ -42,7 +42,9 @@ import sonia.scm.SCMContext; /** * * @author Sebastian Sdorra + * @deprecated */ +@Deprecated public class SecurityUtil { diff --git a/scm-webapp/src/main/resources/logback.default.xml b/scm-webapp/src/main/resources/logback.default.xml index 8fa2d5cb5a..04e4a8c34a 100644 --- a/scm-webapp/src/main/resources/logback.default.xml +++ b/scm-webapp/src/main/resources/logback.default.xml @@ -50,7 +50,7 @@ - + diff --git a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java index 6b710709c0..984dd8b105 100644 --- a/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/repository/DefaultRepositoryManagerTest.java @@ -37,6 +37,9 @@ package sonia.scm.repository; import com.google.inject.Provider; +import org.apache.shiro.subject.Subject; + +import org.junit.Before; import org.junit.Test; import sonia.scm.Type; @@ -93,6 +96,20 @@ public class DefaultRepositoryManagerTest extends RepositoryManagerTestBase assertNull(m.getFromUri("/git/project1/test-3/ka/some/path")); } + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + */ + @Before + public void setAdminSubject() + { + Subject admin = MockUtil.createAdminSubject(); + + setSubject(admin); + } + //~--- methods -------------------------------------------------------------- /** @@ -139,8 +156,8 @@ public class DefaultRepositoryManagerTest extends RepositoryManagerTestBase ScmConfiguration configuration = new ScmConfiguration(); return new DefaultRepositoryManager(configuration, contextProvider, - new DefaultKeyGenerator(), MockUtil.getAdminSecurityContextProvider(), - repositoryDAO, handlerSet, listenerProvider, hookProvider); + new DefaultKeyGenerator(), repositoryDAO, handlerSet, listenerProvider, + hookProvider); } /** diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/SecurityContextProvider.java b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionResolverTest.java similarity index 54% rename from scm-webapp/src/main/java/sonia/scm/web/security/SecurityContextProvider.java rename to scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionResolverTest.java index fae4f1dfb3..0b07d8992e 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/SecurityContextProvider.java +++ b/scm-webapp/src/test/java/sonia/scm/security/RepositoryPermissionResolverTest.java @@ -30,79 +30,74 @@ */ - -package sonia.scm.web.security; +package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- -import com.google.inject.Inject; -import com.google.inject.Provider; -import com.google.inject.name.Named; +import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import sonia.scm.repository.PermissionType; + +import static org.junit.Assert.*; /** * * @author Sebastian Sdorra */ -public class SecurityContextProvider implements Provider +public class RepositoryPermissionResolverTest { - /** the logger for SecurityContextProvider */ - private static final Logger logger = - LoggerFactory.getLogger(SecurityContextProvider.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param sessionContext - * @param localContext - */ - @Inject - public SecurityContextProvider( - @Named("userSession") Provider sessionContext, - LocalSecurityContextHolder localContext) - { - this.sessionContext = sessionContext; - this.localContext = localContext; - } - - //~--- get methods ---------------------------------------------------------- - /** * Method description * - * - * @return */ - @Override - public WebSecurityContext get() + @Test + public void testResolvePermission() { - WebSecurityContext context = localContext.get(); + RepositoryPermissionResolver resolver = new RepositoryPermissionResolver(); + RepositoryPermission p = resolver.resolvePermission("repository:scm:read"); - if (context == null) - { - context = sessionContext.get(); - } - else if (logger.isDebugEnabled()) - { - String user = SecurityUtil.getUsername(sessionContext); + assertNotNull(p); + assertEquals("scm", p.getRepositoryId()); + assertEquals(PermissionType.READ, p.getPermissionType()); - logger.debug("return system session for user {}", user); - } + p = resolver.resolvePermission("repository:asd:wRitE"); + assertNotNull(p); + assertEquals("asd", p.getRepositoryId()); + assertEquals(PermissionType.WRITE, p.getPermissionType()); - return context; + p = resolver.resolvePermission("repository:*:OWNER"); + assertNotNull(p); + assertEquals("*", p.getRepositoryId()); + assertEquals(PermissionType.OWNER, p.getPermissionType()); } - //~--- fields --------------------------------------------------------------- + /** + * Method description + * + */ + @Test + public void testResolveUnknownPermission() + { + RepositoryPermissionResolver resolver = new RepositoryPermissionResolver(); + RepositoryPermission p = resolver.resolvePermission("user:scm:read"); - /** Field description */ - private LocalSecurityContextHolder localContext; + assertNull(p); - /** Field description */ - private Provider sessionContext; + p = resolver.resolvePermission("group:asd:wRitE"); + assertNull(p); + } + + /** + * Method description + * + */ + @Test + public void testResolveUnknownTypePermission() + { + RepositoryPermissionResolver resolver = new RepositoryPermissionResolver(); + RepositoryPermission p = resolver.resolvePermission("repository:scm:asd"); + + assertNull(p); + } } diff --git a/scm-webapp/src/test/java/sonia/scm/template/FreemarkerTemplateEngineTest.java b/scm-webapp/src/test/java/sonia/scm/template/FreemarkerTemplateEngineTest.java index afc29bc487..ecb9b6270a 100644 --- a/scm-webapp/src/test/java/sonia/scm/template/FreemarkerTemplateEngineTest.java +++ b/scm-webapp/src/test/java/sonia/scm/template/FreemarkerTemplateEngineTest.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/test/java/sonia/scm/template/FreemarkerTemplateTest.java b/scm-webapp/src/test/java/sonia/scm/template/FreemarkerTemplateTest.java index 03a42a1880..7a2fc17606 100644 --- a/scm-webapp/src/test/java/sonia/scm/template/FreemarkerTemplateTest.java +++ b/scm-webapp/src/test/java/sonia/scm/template/FreemarkerTemplateTest.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; //~--- non-JDK imports -------------------------------------------------------- diff --git a/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateEngineTest.java b/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateEngineTest.java index b48082c660..4d1d15de49 100644 --- a/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateEngineTest.java +++ b/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateEngineTest.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; //~--- JDK imports ------------------------------------------------------------ diff --git a/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTest.java b/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTest.java index dea361fc9f..186b809367 100644 --- a/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTest.java +++ b/scm-webapp/src/test/java/sonia/scm/template/MustacheTemplateTest.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; //~--- non-JDK imports -------------------------------------------------------- diff --git a/scm-webapp/src/test/java/sonia/scm/template/TemplateEngineTestBase.java b/scm-webapp/src/test/java/sonia/scm/template/TemplateEngineTestBase.java index f3d9714863..0be0908581 100644 --- a/scm-webapp/src/test/java/sonia/scm/template/TemplateEngineTestBase.java +++ b/scm-webapp/src/test/java/sonia/scm/template/TemplateEngineTestBase.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; //~--- non-JDK imports -------------------------------------------------------- diff --git a/scm-webapp/src/test/java/sonia/scm/template/TemplateTestBase.java b/scm-webapp/src/test/java/sonia/scm/template/TemplateTestBase.java index aa00529fc3..f192617049 100644 --- a/scm-webapp/src/test/java/sonia/scm/template/TemplateTestBase.java +++ b/scm-webapp/src/test/java/sonia/scm/template/TemplateTestBase.java @@ -1,34 +1,35 @@ /** - * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * 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 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. + * 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.template; //~--- non-JDK imports -------------------------------------------------------- diff --git a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java index 840aae469c..d18dac8bb6 100644 --- a/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/user/DefaultUserManagerTest.java @@ -48,6 +48,7 @@ import static org.mockito.Mockito.*; import java.util.HashSet; import java.util.Set; +import org.junit.Before; /** * @@ -55,6 +56,11 @@ import java.util.Set; */ public class DefaultUserManagerTest extends UserManagerTestBase { + + @Before + public void setAdminSubject(){ + setSubject(MockUtil.createAdminSubject()); + } /** * Method description @@ -74,8 +80,7 @@ public class DefaultUserManagerTest extends UserManagerTestBase when(listenerProvider.get()).thenReturn(new HashSet()); XmlUserDAO userDAO = new XmlUserDAO(factory); - - return new DefaultUserManager(MockUtil.getAdminSecurityContextProvider(), - userDAO, listenerProvider); + + return new DefaultUserManager(userDAO, listenerProvider); } } diff --git a/scm-webapp/src/test/java/sonia/scm/web/security/DefaultAuthenticationHandlerTest.java b/scm-webapp/src/test/java/sonia/scm/web/security/DefaultAuthenticationHandlerTest.java index e9a2cc42b9..cdd3c2a116 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/security/DefaultAuthenticationHandlerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/security/DefaultAuthenticationHandlerTest.java @@ -44,10 +44,11 @@ import sonia.scm.security.EncryptionHandler; import sonia.scm.security.MessageDigestEncryptionHandler; import sonia.scm.store.JAXBStoreFactory; import sonia.scm.store.StoreFactory; +import sonia.scm.user.DefaultUserManager; import sonia.scm.user.User; import sonia.scm.user.UserListener; import sonia.scm.user.UserTestData; -import sonia.scm.user.DefaultUserManager; +import sonia.scm.user.xml.XmlUserDAO; import sonia.scm.util.MockUtil; import static org.junit.Assert.*; @@ -61,7 +62,6 @@ import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import sonia.scm.user.xml.XmlUserDAO; /** * @@ -141,13 +141,16 @@ public class DefaultAuthenticationHandlerTest extends AbstractTestBase when(listenerProvider.get()).thenReturn(new HashSet()); XmlUserDAO userDAO = new XmlUserDAO(storeFactory); - - DefaultUserManager userManager = - new DefaultUserManager(MockUtil.getAdminSecurityContextProvider(), - userDAO, listenerProvider); + + setSubject(MockUtil.createAdminSubject()); + + DefaultUserManager userManager = new DefaultUserManager(userDAO, + listenerProvider); userManager.init(contextProvider); userManager.create(slarti); + clearSubject(); + handler = new DefaultAuthenticationHandler(userManager, enc); handler.init(contextProvider); request = MockUtil.getHttpServletRequest(); diff --git a/scm-webapp/src/test/resources/sonia/scm/template/002.ftl b/scm-webapp/src/test/resources/sonia/scm/template/002.ftl index 4551c970e8..70257cdb5c 100644 --- a/scm-webapp/src/test/resources/sonia/scm/template/002.ftl +++ b/scm-webapp/src/test/resources/sonia/scm/template/002.ftl @@ -1 +1,33 @@ +<#-- + + 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 + + +--> Hello ${name}! \ No newline at end of file diff --git a/scm-webapp/src/test/resources/sonia/scm/template/004.ftl b/scm-webapp/src/test/resources/sonia/scm/template/004.ftl index dfa2f00394..b83d5a6f4a 100644 --- a/scm-webapp/src/test/resources/sonia/scm/template/004.ftl +++ b/scm-webapp/src/test/resources/sonia/scm/template/004.ftl @@ -1 +1,33 @@ +<#-- + + 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 + + +--> Hello ${notAvailable}! \ No newline at end of file diff --git a/scm-webapp/src/test/resources/sonia/scm/template/006.ftl b/scm-webapp/src/test/resources/sonia/scm/template/006.ftl index 99d266c61a..70f5780e79 100644 --- a/scm-webapp/src/test/resources/sonia/scm/template/006.ftl +++ b/scm-webapp/src/test/resources/sonia/scm/template/006.ftl @@ -1 +1,33 @@ +<#-- + + 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 + + +--> Hello ! \ No newline at end of file