merge with branch issue-781

This commit is contained in:
Sebastian Sdorra
2017-04-26 08:47:49 +02:00
27 changed files with 2436 additions and 1096 deletions

View File

@@ -0,0 +1,89 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import sonia.scm.event.Event;
/**
* This type of event is fired whenever a authorization relevant data changes. This event
* is especially useful for cache invalidation.
*
* @author Sebastian Sdorra
* @since 1.52
*/
@Event
public final class AuthorizationChangedEvent {
private final String nameOfAffectedUser;
private AuthorizationChangedEvent(String nameOfAffectedUser) {
this.nameOfAffectedUser = nameOfAffectedUser;
}
/**
* Returns {@code true} if every user is affected by this data change.
*
* @return {@code true} if every user is affected
*/
public boolean isEveryUserAffected(){
return nameOfAffectedUser != null;
}
/**
* Returns the name of the user which is affected by this event.
*
* @return name of affected user
*/
public String getNameOfAffectedUser(){
return nameOfAffectedUser;
}
/**
* Creates a new event which affects every user.
*
* @return new event for every user
*/
public static AuthorizationChangedEvent createForEveryUser() {
return new AuthorizationChangedEvent(null);
}
/**
* Create a new event which affect a single user.
*
* @param nameOfAffectedUser name of affected user
*
* @return new event for a single user
*/
public static AuthorizationChangedEvent createForUser(String nameOfAffectedUser) {
return new AuthorizationChangedEvent(nameOfAffectedUser);
}
}

View File

@@ -166,6 +166,7 @@ import sonia.scm.net.ahc.JsonContentTransformer;
import sonia.scm.net.ahc.XmlContentTransformer;
import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler;
import sonia.scm.security.AuthorizationChangedEventProducer;
import sonia.scm.security.XsrfProtectionFilter;
import sonia.scm.web.UserAgentParser;
@@ -300,6 +301,7 @@ public class ScmServletModule extends ServletModule
pluginLoader.processExtensions(binder());
// bind security stuff
bind(AuthorizationChangedEventProducer.class);
bind(PermissionResolver.class, RepositoryPermissionResolver.class);
bind(AuthenticationManager.class, ChainAuthenticatonManager.class);
bind(SecurityContext.class).to(BasicSecurityContext.class);
@@ -310,6 +312,7 @@ public class ScmServletModule extends ServletModule
// bind cache
bind(CacheManager.class, GuavaCacheManager.class);
bind(org.apache.shiro.cache.CacheManager.class, GuavaCacheManager.class);
// bind dao
bind(GroupDAO.class, XmlGroupDAO.class);
@@ -386,8 +389,7 @@ public class ScmServletModule extends ServletModule
filter(PATTERN_ALL).through(BaseUrlFilter.class);
filter(PATTERN_ALL).through(AutoLoginFilter.class);
filterRegex(RESOURCE_REGEX).through(GZipFilter.class);
filter(PATTERN_RESTAPI,
PATTERN_DEBUG).through(ApiBasicAuthenticationFilter.class);
filter(PATTERN_RESTAPI, PATTERN_DEBUG).through(ApiBasicAuthenticationFilter.class);
filter(PATTERN_RESTAPI, PATTERN_DEBUG).through(SecurityFilter.class);
filter(PATTERN_CONFIG, PATTERN_ADMIN).through(AdminSecurityFilter.class);
@@ -434,11 +436,7 @@ public class ScmServletModule extends ServletModule
UriExtensionsConfig.class.getName());
String restPath = getRestPackages();
if (logger.isInfoEnabled())
{
logger.info("configure jersey with package path: {}", restPath);
}
logger.info("configure jersey with package path: {}", restPath);
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, restPath);
serve(PATTERN_RESTAPI).with(GuiceContainer.class, params);

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.cache;
import com.google.common.cache.Cache;
import com.google.common.collect.Sets;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.Filter;
/**
* Implementation of basic cache methods for guava based caches.
*
* @author Sebastian Sdorra
*
* @since 1.52
*
* @param <K> key
* @param <V> value
*/
public abstract class GuavaBaseCache<K, V> {
/**
* the logger for GuavaBaseCache
*/
private static final Logger logger = LoggerFactory.getLogger(GuavaCache.class);
protected com.google.common.cache.Cache<K, V> cache;
protected CopyStrategy copyStrategy = CopyStrategy.NONE;
private final String name;
GuavaBaseCache(com.google.common.cache.Cache<K, V> cache, CopyStrategy copyStrategy, String name) {
this.cache = cache;
this.name = name;
if (copyStrategy != null) {
this.copyStrategy = copyStrategy;
}
}
//~--- methods --------------------------------------------------------------
public void clear() {
logger.debug("clear cache {}", name);
cache.invalidateAll();
}
public boolean contains(K key) {
return cache.getIfPresent(key) != null;
}
public boolean removeAll(Filter<K> filter) {
Set<K> keysToRemove = Sets.newHashSet();
for (K key : cache.asMap().keySet()) {
if (filter.accept(key)) {
keysToRemove.add(key);
}
}
boolean result = false;
if (!keysToRemove.isEmpty()) {
cache.invalidateAll(keysToRemove);
result = true;
}
return result;
}
public V get(K key) {
V value = cache.getIfPresent(key);
if (value != null) {
value = copyStrategy.copyOnRead(value);
}
return value;
}
public Cache<K, V> getWrappedCache() {
return cache;
}
}

View File

@@ -34,18 +34,6 @@ package sonia.scm.cache;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.Filter;
//~--- JDK imports ------------------------------------------------------------
import java.util.Set;
/**
*
* @author Sebastian Sdorra
@@ -53,185 +41,22 @@ import java.util.Set;
* @param <K>
* @param <V>
*/
public class GuavaCache<K, V> implements Cache<K, V>
{
public class GuavaCache<K, V> extends GuavaBaseCache<K, V> implements Cache<K, V> {
/**
* the logger for GuavaCache
*/
private static final Logger logger =
LoggerFactory.getLogger(GuavaCache.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param configuration
*/
public GuavaCache(GuavaNamedCacheConfiguration configuration)
{
this(configuration, configuration.getName());
GuavaCache(com.google.common.cache.Cache<K, V> cache, CopyStrategy copyStrategy, String name) {
super(cache, copyStrategy, name);
}
/**
* Constructs ...
*
*
* @param configuration
* @param name
*/
public GuavaCache(GuavaCacheConfiguration configuration, String name)
{
this(GuavaCaches.create(configuration, name),
configuration.getCopyStrategy(), name);
}
/**
* Constructs ...
*
*
* @param cache
* @param copyStrategy
* @param name
*/
@VisibleForTesting
protected GuavaCache(com.google.common.cache.Cache<K, V> cache,
CopyStrategy copyStrategy, String name)
{
this.cache = cache;
this.name = name;
if (copyStrategy != null)
{
this.copyStrategy = copyStrategy;
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*/
@Override
public void clear()
{
if (logger.isDebugEnabled())
{
logger.debug("clear cache {}", name);
}
cache.invalidateAll();
}
/**
* Method description
*
*
* @param key
*
* @return
*/
@Override
public boolean contains(K key)
{
return cache.getIfPresent(key) != null;
}
/**
* Method description
*
*
* @param key
* @param value
*/
@Override
public void put(K key, V value)
{
public void put(K key, V value) {
cache.put(key, copyStrategy.copyOnWrite(value));
}
/**
* Method description
*
*
* @param key
*
* @return
*/
@Override
public boolean remove(K key)
{
public boolean remove(K key) {
cache.invalidate(key);
return true;
}
/**
* Method description
*
*
* @param filter
*
* @return
*/
@Override
public boolean removeAll(Filter<K> filter)
{
Set<K> keysToRemove = Sets.newHashSet();
for (K key : cache.asMap().keySet())
{
if (filter.accept(key))
{
keysToRemove.add(key);
}
}
boolean result = false;
if (!keysToRemove.isEmpty())
{
cache.invalidateAll(keysToRemove);
result = true;
}
return result;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param key
*
* @return
*/
@Override
public V get(K key)
{
V value = cache.getIfPresent(key);
if (value != null)
{
value = copyStrategy.copyOnRead(value);
}
return value;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private com.google.common.cache.Cache<K, V> cache;
/** Field description */
private CopyStrategy copyStrategy = CopyStrategy.NONE;
/** Field description */
private String name;
}

View File

@@ -47,66 +47,52 @@ import java.io.IOException;
import java.util.Map;
import org.apache.shiro.cache.CacheException;
/**
* Guava based implementation of {@link CacheManager} and {@link org.apache.shiro.cache.CacheManager}.
*
* @author Sebastian Sdorra
*/
@Singleton
public class GuavaCacheManager implements CacheManager
public class GuavaCacheManager implements CacheManager, org.apache.shiro.cache.CacheManager
{
/**
* the logger for GuavaCacheManager
*/
private static final Logger logger =
LoggerFactory.getLogger(GuavaCacheManager.class);
private static final Logger logger = LoggerFactory.getLogger(GuavaCacheManager.class);
private volatile Map<String, CacheWithConfiguration> cacheMap = Maps.newHashMap();
private GuavaCacheConfiguration defaultConfiguration;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
public GuavaCacheManager()
{
public GuavaCacheManager() {
this(GuavaCacheConfigurationReader.read());
}
/**
* Constructs ...
*
*
* @param config
*/
@VisibleForTesting
protected GuavaCacheManager(GuavaCacheManagerConfiguration config)
{
protected GuavaCacheManager(GuavaCacheManagerConfiguration config) {
defaultConfiguration = config.getDefaultCache();
for (GuavaNamedCacheConfiguration ncc : config.getCaches())
{
logger.debug("create cache {} from configured configuration {}",
ncc.getName(), ncc);
cacheMap.put(ncc.getName(), new GuavaCache(ncc));
for (GuavaNamedCacheConfiguration ncc : config.getCaches()) {
logger.debug("create cache {} from configured configuration {}", ncc.getName(), ncc);
cacheMap.put(ncc.getName(), new CacheWithConfiguration(
GuavaCaches.create(defaultConfiguration, ncc.getName()),
defaultConfiguration)
);
}
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @throws IOException
*/
@Override
public void close() throws IOException
{
public void close() throws IOException {
logger.info("close guava cache manager");
for (Cache c : cacheMap.values())
{
c.clear();
for (CacheWithConfiguration c : cacheMap.values()) {
c.cache.invalidateAll();
}
cacheMap.clear();
@@ -114,43 +100,44 @@ public class GuavaCacheManager implements CacheManager
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param key
* @param value
* @param name
* @param <K>
* @param <V>
*
* @return
*/
@Override
public synchronized <K, V> GuavaCache<K, V> getCache(Class<K> key,
Class<V> value, String name)
{
private synchronized <K, V> CacheWithConfiguration<K, V> getCacheWithConfiguration(String name) {
logger.trace("try to retrieve cache {}", name);
GuavaCache<K, V> cache = cacheMap.get(name);
CacheWithConfiguration<K, V> cache = cacheMap.get(name);
if (cache == null)
{
if (cache == null) {
logger.debug(
"cache {} does not exists, creating a new instance from default configuration: {}",
name, defaultConfiguration);
cache = new GuavaCache<K, V>(defaultConfiguration, name);
name, defaultConfiguration
);
cache = new CacheWithConfiguration(GuavaCaches.create(defaultConfiguration, name), defaultConfiguration);
cacheMap.put(name, cache);
}
return cache;
}
//~--- fields ---------------------------------------------------------------
@Override
public <K, V> GuavaCache<K, V> getCache(Class<K> key, Class<V> value, String name) {
CacheWithConfiguration<K, V> cw = getCacheWithConfiguration(name);
return new GuavaCache<>(cw.cache, cw.configuration.getCopyStrategy(), name);
}
/** Field description */
private volatile Map<String, GuavaCache> cacheMap = Maps.newHashMap();
@Override
public <K, V> GuavaSecurityCache<K, V> getCache(String name) throws CacheException {
CacheWithConfiguration<K, V> cw = getCacheWithConfiguration(name);
return new GuavaSecurityCache<>(cw.cache, cw.configuration.getCopyStrategy(), name);
}
/** Field description */
private GuavaCacheConfiguration defaultConfiguration;
private static class CacheWithConfiguration<K,V> {
private final com.google.common.cache.Cache<K,V> cache;
private final GuavaCacheConfiguration configuration;
private CacheWithConfiguration(com.google.common.cache.Cache<K, V> cache, GuavaCacheConfiguration configuration) {
this.cache = cache;
this.configuration = configuration;
}
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.cache;
import com.google.common.cache.Cache;
import java.util.Collection;
import java.util.Set;
import org.apache.shiro.cache.CacheException;
/**
* Guava based implementation of {@link org.apache.shiro.cache.Cache}.
*
* @author Sebastian Sdorra
*
* @since 1.52
*
* @param <K>
* @param <V>
*/
public class GuavaSecurityCache<K, V> extends GuavaBaseCache<K, V> implements org.apache.shiro.cache.Cache<K, V> {
GuavaSecurityCache(Cache<K, V> cache, CopyStrategy copyStrategy, String name) {
super(cache, copyStrategy, name);
}
@Override
public V put(K key, V value) throws CacheException {
V previousValue = cache.getIfPresent(key);
cache.put(key, value);
return previousValue;
}
@Override
public V remove(K key) throws CacheException {
V previousValue = cache.getIfPresent(key);
cache.invalidate(key);
return previousValue;
}
@Override
public int size() {
return (int) cache.size();
}
@Override
public Set<K> keys() {
return cache.asMap().keySet();
}
@Override
public Collection<V> values() {
return cache.asMap().values();
}
}

View File

@@ -0,0 +1,105 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.user.User;
import sonia.scm.util.Util;
/**
* Detects administrator from configuration.
*
* @author Sebastian Sdorra
* @since 1.52
*/
public class AdminDetector {
/**
* the logger for AdminDetector
*/
private static final Logger LOG = LoggerFactory.getLogger(AdminDetector.class);
private final ScmConfiguration configuration;
/**
* Constructs admin detector.
*
* @param configuration scm-manager main configuration
*/
@Inject
public AdminDetector(ScmConfiguration configuration) {
this.configuration = configuration;
}
/**
* Checks is the authenticated user is marked as administrator by {@link ScmConfiguration}.
*
* @param user authenticated user
* @param groups groups of authenticated user
*/
public void checkForAuthenticatedAdmin(User user, Set<String> groups) {
if (!user.isAdmin()) {
user.setAdmin(isAdminByConfiguration(user, groups));
if (LOG.isDebugEnabled() && user.isAdmin()) {
LOG.debug("user {} is marked as admin by configuration", user.getName());
}
}
else if (LOG.isDebugEnabled()) {
LOG.debug("authenticator {} marked user {} as admin", user.getType(), user.getName());
}
}
private boolean isAdminByConfiguration(User user, Collection<String> groups) {
boolean result = false;
Set<String> adminUsers = configuration.getAdminUsers();
if (adminUsers != null) {
result = adminUsers.contains(user.getName());
}
if (!result) {
Set<String> adminGroups = configuration.getAdminGroups();
if (adminGroups != null) {
result = Util.containsOne(adminGroups, groups);
}
}
return result;
}
}

View File

@@ -0,0 +1,124 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.inject.Inject;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.group.GroupNames;
import sonia.scm.user.User;
import sonia.scm.web.security.AuthenticationResult;
/**
* Collects authentication info for realm.
*
* @author Sebastian Sdorra
* @since 1.52
*/
public class AuthenticationInfoCollector {
/**
* the logger for AuthenticationInfoCollector
*/
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationInfoCollector.class);
private final LocalDatabaseSynchronizer synchronizer;
private final GroupCollector groupCollector;
private final CredentialsStore sessionStore;
/**
* Construct a new AuthenticationInfoCollector.
*
* @param synchronizer local database synchronizer
* @param groupCollector groups collector
* @param credentialsStore credentials store
*/
@Inject
public AuthenticationInfoCollector(
LocalDatabaseSynchronizer synchronizer, GroupCollector groupCollector, CredentialsStore credentialsStore
) {
this.synchronizer = synchronizer;
this.groupCollector = groupCollector;
this.sessionStore = credentialsStore;
}
/**
* Creates authentication info from token and authentication result.
*
* @param token username and password token
* @param authenticationResult authentication result
*
* @return authentication info
*/
public AuthenticationInfo createAuthenticationInfo(
UsernamePasswordToken token, AuthenticationResult authenticationResult
) {
User user = authenticationResult.getUser();
Set<String> groups = groupCollector.collectGroups(authenticationResult);
synchronizer.synchronize(user, groups);
if (isUserDisabled(user)) {
throwAccountIsDisabledExceptionAndLog(user.getName());
}
PrincipalCollection collection = createPrincipalCollection(user, groups);
AuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(collection, token.getPassword());
sessionStore.store(token);
return authenticationInfo;
}
private PrincipalCollection createPrincipalCollection(User user, Set<String> groups) {
SimplePrincipalCollection collection = new SimplePrincipalCollection();
collection.add(user.getId(), ScmRealm.NAME);
collection.add(user, ScmRealm.NAME);
collection.add(new GroupNames(groups), ScmRealm.NAME);
return collection;
}
private boolean isUserDisabled(User user) {
return !user.isActive();
}
private void throwAccountIsDisabledExceptionAndLog(String username) {
String msg = "user ".concat(username).concat(" is deactivated");
LOG.warn(msg);
throw new DisabledAccountException(msg);
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.inject.Inject;
import com.google.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.authc.UsernamePasswordToken;
import sonia.scm.web.security.AuthenticationManager;
import sonia.scm.web.security.AuthenticationResult;
/**
* Facade for the authentication process. The main reason for this facade is to reduce the number of constructor
* parameters on the realm. This should improve testability.
*
* @author Sebastian Sdorra
* @since 1.52
*/
public class AuthenticatorFacade {
private final AuthenticationManager authenticator;
private final Provider<HttpServletRequest> requestProvider;
private final Provider<HttpServletResponse> responseProvider;
@Inject
public AuthenticatorFacade(AuthenticationManager authenticator, Provider<HttpServletRequest> requestProvider,
Provider<HttpServletResponse> responseProvider) {
this.authenticator = authenticator;
this.requestProvider = requestProvider;
this.responseProvider = responseProvider;
}
/**
* Delegates the authentication request to the injected implementation of the {@link AuthenticationManager}.
*
* @param token username password token
*
* @return authentication result
*/
public AuthenticationResult authenticate(UsernamePasswordToken token) {
return authenticator.authenticate(
requestProvider.get(),
responseProvider.get(),
token.getUsername(),
new String(token.getPassword())
);
}
}

View File

@@ -0,0 +1,271 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;
import sonia.scm.ModificationHandlerEvent;
import sonia.scm.event.HandlerEventBase;
import sonia.scm.event.ScmEventBus;
import sonia.scm.group.Group;
import sonia.scm.group.GroupEvent;
import sonia.scm.group.GroupModificationEvent;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryEvent;
import sonia.scm.repository.RepositoryModificationEvent;
import sonia.scm.user.User;
import sonia.scm.user.UserEvent;
import sonia.scm.user.UserModificationEvent;
/**
* Receives all kinds of events, which affects authorization relevant data and fires an
* {@link AuthorizationChangedEvent} if authorization data has changed.
*
* @author Sebastian Sdorra
* @since 1.52
*/
@EagerSingleton
public class AuthorizationChangedEventProducer {
/**
* the logger for AuthorizationChangedEventProducer
*/
private static final Logger logger = LoggerFactory.getLogger(AuthorizationChangedEventProducer.class);
/**
* Constructs a new instance.
*/
public AuthorizationChangedEventProducer() {
}
/**
* Invalidates the cache of a user which was modified. The cache entries for the user will be invalidated for the
* following reasons:
* <ul>
* <li>Admin or Active flag was modified.</li>
* <li>New user created, for the case of old cache values</li>
* <li>User deleted</li>
* </ul>
*
* @param event user event
*/
@Subscribe
public void onEvent(UserEvent event) {
if (event.getEventType().isPost()) {
if (isModificationEvent(event)) {
handleUserModificationEvent((UserModificationEvent) event);
} else {
handleUserEvent(event);
}
}
}
private boolean isModificationEvent(HandlerEventBase<?> event) {
return event instanceof ModificationHandlerEvent;
}
private void handleUserEvent(UserEvent event) {
String username = event.getItem().getName();
logger.debug(
"fire authorization changed event for user {}, because of user {} event", username, event.getEventType()
);
fireEventForUser(username);
}
private void handleUserModificationEvent(UserModificationEvent event) {
String username = event.getItem().getId();
User beforeModification = event.getItemBeforeModification();
if (isAuthorizationDataModified(event.getItem(), beforeModification)) {
logger.debug(
"fire authorization changed event for user {}, because of a authorization relevant field has changed",
username
);
fireEventForUser(username);
} else {
logger.debug(
"authorization changed event for user {} is not fired, because no authorization relevant field has changed",
username
);
}
}
private boolean isAuthorizationDataModified(User user, User beforeModification) {
return user.isAdmin() != beforeModification.isAdmin() || user.isActive() != beforeModification.isActive();
}
private void fireEventForUser(String username) {
sendEvent(AuthorizationChangedEvent.createForUser(username));
}
/**
* Invalidates the whole cache, if a repository has changed. The cache get cleared for one of the following reasons:
* <ul>
* <li>New repository created</li>
* <li>Repository was removed</li>
* <li>Archived, Public readable or permission field of the repository was modified</li>
* </ul>
*
* @param event repository event
*/
@Subscribe
public void onEvent(RepositoryEvent event) {
if (event.getEventType().isPost()) {
if (isModificationEvent(event)) {
handleRepositoryModificationEvent((RepositoryModificationEvent) event);
} else {
handleRepositoryEvent(event);
}
}
}
private void handleRepositoryModificationEvent(RepositoryModificationEvent event) {
Repository repository = event.getItem();
if (isAuthorizationDataModified(repository, event.getItemBeforeModification())) {
logger.debug(
"fire authorization changed event, because a relevant field of repository {} has changed", repository.getName()
);
fireEventForEveryUser();
} else {
logger.debug(
"authorization changed event is not fired, because non relevant field of repository {} has changed",
repository.getName()
);
}
}
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
return repository.isArchived() != beforeModification.isArchived()
|| repository.isPublicReadable() != beforeModification.isPublicReadable()
|| ! repository.getPermissions().equals(beforeModification.getPermissions());
}
private void fireEventForEveryUser() {
sendEvent(AuthorizationChangedEvent.createForEveryUser());
}
private void handleRepositoryEvent(RepositoryEvent event){
logger.debug(
"fire authorization changed event, because of received {} event for repository {}",
event.getEventType(), event.getItem().getName()
);
fireEventForEveryUser();
}
/**
* Invalidates the whole cache if a group permission has changed and invalidates the cached entries of a user, if a
* user permission has changed.
*
* @param event permission event
*/
@Subscribe
public void onEvent(StoredAssignedPermissionEvent event) {
if (event.getEventType().isPost()) {
StoredAssignedPermission permission = event.getPermission();
if (permission.isGroupPermission()) {
handleGroupPermissionChange(permission);
} else {
handleUserPermissionChange(permission);
}
}
}
private void handleGroupPermissionChange(StoredAssignedPermission permission) {
logger.debug(
"fire authorization changed event, because global group permission {} has changed",
permission.getId()
);
fireEventForEveryUser();
}
private void handleUserPermissionChange(StoredAssignedPermission permission) {
logger.debug(
"fire authorization changed event for user {}, because permission {} has changed",
permission.getName(), permission.getId()
);
fireEventForUser(permission.getName());
}
/**
* Invalidates the whole cache, if a group has changed. The cache get cleared for one of the following reasons:
* <ul>
* <li>New group created</li>
* <li>Group was removed</li>
* <li>Group members was modified</li>
* </ul>
*
* @param event group event
*/
@Subscribe
public void onEvent(GroupEvent event) {
if (event.getEventType().isPost()) {
if (isModificationEvent(event)) {
handleGroupModificationEvent((GroupModificationEvent) event);
} else {
handleGroupEvent(event);
}
}
}
private void handleGroupModificationEvent(GroupModificationEvent event) {
Group group = event.getItem();
if (isAuthorizationDataModified(group, event.getItemBeforeModification())) {
logger.debug("fire authorization changed event, because group {} has changed", group.getId());
fireEventForEveryUser();
} else {
logger.debug(
"authorization changed event is not fired, because non relevant field of group {} has changed",
group.getId()
);
}
}
private boolean isAuthorizationDataModified(Group group, Group beforeModification) {
return !group.getMembers().equals(beforeModification.getMembers());
}
private void handleGroupEvent(GroupEvent event){
logger.debug(
"fire authorization changed event, because of received group event {} for group {}",
event.getEventType(),
event.getItem().getId()
);
fireEventForEveryUser();
}
@VisibleForTesting
protected void sendEvent(AuthorizationChangedEvent event) {
ScmEventBus.getInstance().post(event);
}
}

View File

@@ -57,14 +57,11 @@ import org.slf4j.LoggerFactory;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.group.GroupEvent;
import sonia.scm.group.GroupNames;
import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryEvent;
import sonia.scm.user.User;
import sonia.scm.user.UserEvent;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
@@ -72,10 +69,6 @@ import sonia.scm.util.Util;
import java.util.List;
import java.util.Set;
import sonia.scm.Filter;
import sonia.scm.group.Group;
import sonia.scm.group.GroupModificationEvent;
import sonia.scm.repository.RepositoryModificationEvent;
import sonia.scm.user.UserModificationEvent;
/**
*
@@ -114,8 +107,7 @@ public class AuthorizationCollector
RepositoryDAO repositoryDAO, SecuritySystem securitySystem,
PermissionResolver resolver)
{
this.cache = cacheManager.getCache(CacheKey.class, AuthorizationInfo.class,
CACHE_NAME);
this.cache = cacheManager.getCache(CacheKey.class, AuthorizationInfo.class, CACHE_NAME);
this.repositoryDAO = repositoryDAO;
this.securitySystem = securitySystem;
this.resolver = resolver;
@@ -146,194 +138,14 @@ public class AuthorizationCollector
return authorizationInfo;
}
/**
* Invalidates the cache of a user which was modified. The cache entries for the user will be invalidated for the
* following reasons:
* <ul>
* <li>Admin or Active flag was modified.</li>
* <li>New user created, for the case of old cache values</li>
* <li>User deleted</li>
* </ul>
*
* @param event user event
*/
@Subscribe
public void onEvent(UserEvent event)
{
if (event.getEventType().isPost())
{
User user = event.getItem();
String username = user.getId();
if (event instanceof UserModificationEvent)
{
User beforeModification = ((UserModificationEvent) event).getItemBeforeModification();
if (shouldCacheBeCleared(user, beforeModification))
{
logger.debug("invalidate cache of user {}, because of a permission relevant field has changed", username);
invalidateUserCache(username);
}
else
{
logger.debug("cache of user {} is not invalidated, because no permission relevant field has changed", username);
}
}
else
{
logger.debug("invalidate cache of user {}, because of user {} event", username, event.getEventType());
invalidateUserCache(username);
}
}
}
private boolean shouldCacheBeCleared(User user, User beforeModification)
{
return user.isAdmin() != beforeModification.isAdmin() || user.isActive() != beforeModification.isActive();
}
private void invalidateUserCache(final String username)
{
cache.removeAll(new Filter<CacheKey>()
{
@Override
public boolean accept(CacheKey item)
{
return username.equalsIgnoreCase(item.username);
}
});
}
/**
* Invalidates the whole cache, if a repository has changed. The cache get cleared for one of the following reasons:
* <ul>
* <li>New repository created</li>
* <li>Repository was removed</li>
* <li>Archived, Public readable or permission field of the repository was modified</li>
* </ul>
*
* @param event repository event
*/
@Subscribe
public void onEvent(RepositoryEvent event)
{
if (event.getEventType().isPost())
{
Repository repository = event.getItem();
if (event instanceof RepositoryModificationEvent)
{
Repository beforeModification = ((RepositoryModificationEvent) event).getItemBeforeModification();
if (shouldCacheBeCleared(repository, beforeModification))
{
logger.debug("clear cache, because a relevant field of repository {} has changed", repository.getName());
cache.clear();
}
else
{
logger.debug(
"cache is not invalidated, because non relevant field of repository {} has changed",
repository.getName()
);
}
}
else
{
logger.debug("clear cache, received {} event of repository {}", event.getEventType(), repository.getName());
cache.clear();
}
}
}
private boolean shouldCacheBeCleared(Repository repository, Repository beforeModification)
{
return repository.isArchived() != beforeModification.isArchived()
|| repository.isPublicReadable() != beforeModification.isPublicReadable()
|| ! repository.getPermissions().equals(beforeModification.getPermissions());
}
/**
* Invalidates the whole cache if a group permission has changed and invalidates the cached entries of a user, if a
* user permission has changed.
*
*
* @param event permission event
*/
@Subscribe
public void onEvent(StoredAssignedPermissionEvent event)
{
if (event.getEventType().isPost())
{
StoredAssignedPermission permission = event.getPermission();
if (permission.isGroupPermission())
{
logger.debug("clear cache, because global group permission {} has changed", permission.getId());
cache.clear();
}
else
{
logger.debug(
"clear cache of user {}, because permission {} has changed",
permission.getName(), event.getPermission().getId()
);
invalidateUserCache(permission.getName());
}
}
}
/**
* Invalidates the whole cache, if a group has changed. The cache get cleared for one of the following reasons:
* <ul>
* <li>New group created</li>
* <li>Group was removed</li>
* <li>Group members was modified</li>
* </ul>
*
* @param event group event
*/
@Subscribe
public void onEvent(GroupEvent event)
{
if (event.getEventType().isPost())
{
Group group = event.getItem();
if (event instanceof GroupModificationEvent)
{
Group beforeModification = ((GroupModificationEvent) event).getItemBeforeModification();
if (shouldCacheBeCleared(group, beforeModification))
{
logger.debug("clear cache, because group {} has changed", group.getId());
cache.clear();
}
else
{
logger.debug(
"cache is not invalidated, because non relevant field of group {} has changed",
group.getId()
);
}
}
else
{
logger.debug("clear cache, received group event {} for group {}", event.getEventType(), group.getId());
cache.clear();
}
}
}
private boolean shouldCacheBeCleared(Group group, Group beforeModification)
{
return !group.getMembers().equals(beforeModification.getMembers());
}
/**
* Method description
*
*
*
* @param principals
*
* @return
*/
AuthorizationInfo collect(PrincipalCollection principals)
public AuthorizationInfo collect(PrincipalCollection principals)
{
Preconditions.checkNotNull(principals, "principals parameter is required");
@@ -568,6 +380,30 @@ public class AuthorizationCollector
//J+
}
@Subscribe
public void invalidateCache(AuthorizationChangedEvent event) {
if (event.isEveryUserAffected()) {
invalidateUserCache(event.getNameOfAffectedUser());
} else {
invalidateCache();
}
}
private void invalidateUserCache(final String username) {
logger.info("invalidate cache for user {}, because of a received authorization event", username);
cache.removeAll(new Filter<CacheKey>() {
@Override
public boolean accept(CacheKey item) {
return username.equalsIgnoreCase(item.username);
}
});
}
private void invalidateCache() {
logger.info("invalidate cache, because of a received authorization event");
cache.clear();
}
//~--- inner classes --------------------------------------------------------
/**

View File

@@ -0,0 +1,80 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import com.google.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* Stores credentials of the user in the http session of the user.
*
* @author Sebastian Sdorra
* @since 1.52
*/
public class CredentialsStore {
@VisibleForTesting
static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
private final Provider<HttpServletRequest> requestProvider;
@Inject
public CredentialsStore(Provider<HttpServletRequest> requestProvider) {
this.requestProvider = requestProvider;
}
/**
* Extracts the user credentials from token, encrypts them, and stores them in the http session.
*
* @param token username password token
*/
public void store(UsernamePasswordToken token) {
// store encrypted credentials in session
String credentials = token.getUsername();
char[] password = token.getPassword();
if (password != null && password.length > 0) {
credentials = credentials.concat(":").concat(new String(password));
}
credentials = encrypt(credentials);
requestProvider.get().getSession(true).setAttribute(SCM_CREDENTIALS, credentials);
}
@VisibleForTesting
protected String encrypt(String credentials){
return CipherUtil.getInstance().encode(credentials);
}
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import java.util.Collection;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.group.GroupNames;
import sonia.scm.user.User;
import sonia.scm.util.Util;
import sonia.scm.web.security.AuthenticationResult;
/**
* Collects groups from {@link GroupManager} after authentication.
*
* @author Sebastian Sdorra
* @since 1.52
*/
public class GroupCollector {
/**
* the logger for GroupCollector
*/
private static final Logger LOG = LoggerFactory.getLogger(GroupCollector.class);
private final GroupManager groupManager;
@Inject
public GroupCollector(GroupManager groupManager) {
this.groupManager = groupManager;
}
/**
* Collect groups from {@link AuthenticationResult} and {@link GroupManager}.
*
* @param authenticationResult authentication result
*
* @return collected groups
*/
public Set<String> collectGroups(AuthenticationResult authenticationResult) {
Set<String> groups = Sets.newHashSet();
// add group for all authenticated users
groups.add(GroupNames.AUTHENTICATED);
// load external groups
Collection<String> groupsFromAuthenticator = authenticationResult.getGroups();
if (groupsFromAuthenticator != null) {
groups.addAll(groupsFromAuthenticator);
}
User user = authenticationResult.getUser();
loadGroupFromDatabase(user, groups);
if (LOG.isDebugEnabled()) {
LOG.debug(createMembershipLogMessage(user, groups));
}
return groups;
}
private void loadGroupFromDatabase(User user, Set<String> groupSet) {
Collection<Group> groupCollection = groupManager.getGroupsForMember(user.getName());
if (groupCollection != null) {
for (Group group : groupCollection) {
groupSet.add(group.getName());
}
}
}
private String createMembershipLogMessage(User user, Set<String> groups) {
StringBuilder msg = new StringBuilder("user ");
msg.append(user.getName());
if (Util.isNotEmpty(groups)) {
msg.append(" is member of ");
Joiner.on(", ").appendTo(msg, groups);
} else {
msg.append(" is not a member of a group");
}
return msg.toString();
}
}

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.inject.Inject;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEvent;
import sonia.scm.user.User;
import sonia.scm.user.UserDAO;
import sonia.scm.user.UserEventHack;
import sonia.scm.user.UserManager;
/**
* Checks and synchronizes authenticated users with local database.
*
* @author Sebastian Sdorra
* @since 1.52
*/
public class LocalDatabaseSynchronizer {
/**
* the logger for LocalDatabaseSynchronizer
*/
private static final Logger logger = LoggerFactory.getLogger(LocalDatabaseSynchronizer.class);
private final AdminDetector adminSelector;
private final UserManager userManager;
private final UserDAO userDAO;
@Inject
public LocalDatabaseSynchronizer(AdminDetector adminSelector, UserManager userManager, UserDAO userDAO) {
this.adminSelector = adminSelector;
this.userManager = userManager;
this.userDAO = userDAO;
}
/**
* Check for disabled and administrator marks on authenticated users and synchronize the state with the local
* database.
*
* @param user authenticated user
* @param groups groups of authenticated user
*/
public void synchronize(User user, Set<String> groups) {
adminSelector.checkForAuthenticatedAdmin(user, groups);
User dbUser = userDAO.get(user.getId());
if (dbUser != null) {
synchronizeWithLocalDatabase(user, dbUser);
} else if (user.isValid()) {
createUserInLocalDatabase(user);
} else {
logger.warn("could not create user {}, beacause it is not valid", user.getName());
}
}
private void synchronizeWithLocalDatabase(User user, User dbUser) {
synchronizeAdminFlag(user, dbUser);
synchronizeActiveFlag(user, dbUser);
modifyUserInLocalDatabase(user, dbUser);
}
private void synchronizeAdminFlag(User user, User dbUser) {
// if database user is an admin, set admin for the current user
if (dbUser.isAdmin()) {
logger.debug("user {} of type {} is marked as admin by local database", user.getName(), user.getType());
user.setAdmin(true);
}
}
private void synchronizeActiveFlag(User user, User dbUser) {
// user is deactivated by database
if (!dbUser.isActive()) {
logger.debug("user {} is marked as deactivated by local database", user.getName());
user.setActive(false);
}
}
private void createUserInLocalDatabase(User user) {
user.setCreationDate(System.currentTimeMillis());
UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_CREATE);
userDAO.add(user);
UserEventHack.fireEvent(userManager, user, HandlerEvent.CREATE);
}
private void modifyUserInLocalDatabase(User user, User dbUser) {
// modify existing user, copy properties except password and admin
if (user.copyProperties(dbUser, false)) {
user.setLastModified(System.currentTimeMillis());
UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_MODIFY);
userDAO.modify(user);
UserEventHack.fireEvent(userManager, user, HandlerEvent.MODIFY);
}
}
}

View File

@@ -35,531 +35,137 @@ package sonia.scm.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
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.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEvent;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.group.GroupNames;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.user.User;
import sonia.scm.user.UserDAO;
import sonia.scm.user.UserEventHack;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.util.Util;
import sonia.scm.web.security.AuthenticationManager;
import sonia.scm.web.security.AuthenticationResult;
import sonia.scm.web.security.AuthenticationState;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* SCM-Manager authentication realm.
*
* @author Sebastian Sdorra
*/
@Singleton
public class ScmRealm extends AuthorizingRealm
{
/** Field description */
public static final String NAME = "scm";
/** Field description */
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
public class ScmRealm extends AuthorizingRealm {
/**
* the logger for ScmRealm
*/
private static final Logger logger = LoggerFactory.getLogger(ScmRealm.class);
//~--- constructors ---------------------------------------------------------
public static final String NAME = "scm";
private final AuthenticatorFacade authenticator;
private final LoginAttemptHandler loginAttemptHandler;
private final AuthenticationInfoCollector authcCollector;
private final AuthorizationCollector authzCollector;
/**
* Constructs ...
* Constructs a new scm realm.
*
* @param configuration
* @param loginAttemptHandler
* @param collector
* @param userManager
* @param groupManager
* @param userDAO
* @param authenticator
* @param manager
* @param requestProvider
* @param responseProvider
* @param cacheManager cache manager
* @param authenticator authenticator facade
* @param loginAttemptHandler login attempt handler
* @param authcCollector authentication info collector
* @param authzCollector authorization collector
*/
@Inject
public ScmRealm(ScmConfiguration configuration,
LoginAttemptHandler loginAttemptHandler, AuthorizationCollector collector,
UserManager userManager, GroupManager groupManager, UserDAO userDAO,
AuthenticationManager authenticator, RepositoryManager manager,
Provider<HttpServletRequest> requestProvider,
Provider<HttpServletResponse> responseProvider)
{
this.configuration = configuration;
this.loginAttemptHandler = loginAttemptHandler;
this.collector = collector;
this.userManager = userManager;
this.groupManager = groupManager;
this.userDAO = userDAO;
public ScmRealm(CacheManager cacheManager,
AuthenticatorFacade authenticator, LoginAttemptHandler loginAttemptHandler,
AuthenticationInfoCollector authcCollector, AuthorizationCollector authzCollector) {
super(cacheManager);
this.authenticator = authenticator;
this.requestProvider = requestProvider;
this.responseProvider = responseProvider;
this.loginAttemptHandler = loginAttemptHandler;
this.authcCollector = authcCollector;
this.authzCollector = authzCollector;
// set token class
setAuthenticationTokenClass(UsernamePasswordToken.class);
// use own custom caching
setCachingEnabled(false);
setAuthenticationCachingEnabled(false);
setAuthorizationCachingEnabled(false);
// set components
setPermissionResolver(new RepositoryPermissionResolver());
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
*
* @param authToken
*
* @return
*
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authToken)
throws AuthenticationException
{
if (!(authToken instanceof UsernamePasswordToken))
{
throw new UnsupportedTokenException("ScmAuthenticationToken is required");
}
protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
return principals.getPrimaryPrincipal();
}
loginAttemptHandler.beforeAuthentication(authToken);
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws AuthenticationException {
UsernamePasswordToken token = castToken(authToken);
UsernamePasswordToken token = (UsernamePasswordToken) authToken;
loginAttemptHandler.beforeAuthentication(token);
AuthenticationResult result = authenticator.authenticate(token);
AuthenticationInfo info = null;
AuthenticationResult result =
authenticator.authenticate(requestProvider.get(), responseProvider.get(),
token.getUsername(), new String(token.getPassword()));
if ((result != null) && (AuthenticationState.SUCCESS == result.getState()))
{
loginAttemptHandler.onSuccessfulAuthentication(authToken, result);
info = createAuthenticationInfo(token, result);
}
else if ((result != null)
&& (AuthenticationState.NOT_FOUND == result.getState()))
{
throw new UnknownAccountException(
"unknown account ".concat(token.getUsername()));
}
else
{
if (isSuccessful(result)) {
loginAttemptHandler.onSuccessfulAuthentication(token, result);
return authcCollector.createAuthenticationInfo(token, result);
} else if (isAccountNotFound(result)) {
throw new UnknownAccountException("unknown account ".concat(token.getUsername()));
} else {
loginAttemptHandler.onUnsuccessfulAuthentication(authToken, result);
throw new AccountException("authentication failed");
}
return info;
}
/**
* Method description
*
*
* @param principals
*
* @return
*/
private UsernamePasswordToken castToken(AuthenticationToken token) {
if (!(token instanceof UsernamePasswordToken)) {
throw new UnsupportedTokenException("UsernamePasswordToken is required");
}
return (UsernamePasswordToken) token;
}
private boolean isSuccessful(AuthenticationResult result) {
return result != null && AuthenticationState.SUCCESS == result.getState();
}
private boolean isAccountNotFound(AuthenticationResult result) {
return result != null && AuthenticationState.NOT_FOUND == result.getState();
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals)
{
return collector.collect(principals);
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
return authzCollector.collect(principals);
}
/**
* Method description
*
*
* @param request
* @param password
* @param ar
*
* @return
*/
private Set<String> authenticate(HttpServletRequest request, String password,
AuthenticationResult ar)
{
Set<String> 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);
@Subscribe
public void invalidateCache(AuthorizationChangedEvent event) {
if (event.isEveryUserAffected()) {
invalidateUserCache(event.getNameOfAffectedUser());
} else {
invalidateCache();
}
}
/**
* Method description
*
*
* @param user
* @param dbUser
*
* @throws IOException
* @throws UserException
*/
private void checkDBForAdmin(User user, User dbUser)
throws UserException, IOException
{
// if database user is an admin, set admin for the current user
if (dbUser.isAdmin())
{
if (logger.isDebugEnabled())
{
logger.debug("user {} of type {} is marked as admin by local database",
user.getName(), user.getType());
}
user.setAdmin(true);
}
// modify existing user, copy properties except password and admin
if (user.copyProperties(dbUser, false))
{
user.setLastModified(System.currentTimeMillis());
UserEventHack.fireEvent(userManager, user, HandlerEvent.BEFORE_MODIFY);
userDAO.modify(user);
UserEventHack.fireEvent(userManager, user, HandlerEvent.MODIFY);
}
private void invalidateUserCache(final String username) {
logger.info("invalidate cache for user {}, because of a received authorization event", username);
getAuthorizationCache().remove(username);
}
/**
* Method description
*
*
* @param user
* @param groupSet
*/
private void checkForAuthenticatedAdmin(User user, Set<String> groupSet)
{
if (!user.isAdmin())
{
user.setAdmin(isAdmin(user, groupSet));
if (logger.isDebugEnabled() && user.isAdmin())
{
logger.debug("user {} is marked as admin by configuration",
user.getName());
}
}
else if (logger.isDebugEnabled())
{
logger.debug("authenticator {} marked user {} as admin", user.getType(),
user.getName());
}
private void invalidateCache() {
logger.info("invalidate cache, because of a received authorization event");
getAuthorizationCache().clear();
}
/**
* Method description
*
*
* @param token
* @param result
*
* @return
*/
private AuthenticationInfo createAuthenticationInfo(
UsernamePasswordToken token, AuthenticationResult result)
{
User user = result.getUser();
Collection<String> groups = authenticate(requestProvider.get(),
new String(token.getPassword()), result);
SimplePrincipalCollection collection = new SimplePrincipalCollection();
/*
* the first (primary) principal should be a unique identifier
*/
collection.add(user.getId(), NAME);
collection.add(user, NAME);
collection.add(new GroupNames(groups), NAME);
return new SimpleAuthenticationInfo(collection, token.getPassword());
}
/**
* Method description
*
*
* @param ar
*
* @return
*/
private Set<String> createGroupSet(AuthenticationResult ar)
{
Set<String> groupSet = Sets.newHashSet();
// add group for all authenticated users
groupSet.add(GroupNames.AUTHENTICATED);
// load external groups
Collection<String> 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<String> groupSet)
{
Collection<Group> 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<String> 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<String> groups)
{
boolean result = false;
Set<String> adminUsers = configuration.getAdminUsers();
if (adminUsers != null)
{
result = adminUsers.contains(user.getName());
}
if (!result)
{
Set<String> adminGroups = configuration.getAdminGroups();
if (adminGroups != null)
{
result = Util.containsOne(adminGroups, groups);
}
}
return result;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final AuthenticationManager authenticator;
/** Field description */
private final AuthorizationCollector collector;
/** Field description */
private final ScmConfiguration configuration;
/** Field description */
private final GroupManager groupManager;
/** Field description */
private final LoginAttemptHandler loginAttemptHandler;
/** Field description */
private final Provider<HttpServletRequest> requestProvider;
/** Field description */
private final Provider<HttpServletResponse> responseProvider;
/** Field description */
private final UserDAO userDAO;
/** Field description */
private final UserManager userManager;
}

View File

@@ -159,8 +159,7 @@ public abstract class CacheManagerTestBase<C extends Cache>
* @param c1
* @param c2
*/
protected void assertIsSame(Cache<String, String> c1,
Cache<String, String> c2)
protected void assertIsSame(Cache<String, String> c1, Cache<String, String> c2)
{
assertSame(c1, c2);
}

View File

@@ -32,6 +32,8 @@
package sonia.scm.cache;
import org.junit.Assert;
/**
*
* @author Sebastian Sdorra
@@ -50,4 +52,14 @@ public class GuavaCacheManagerTest extends CacheManagerTestBase
{
return CacheTestUtil.createDefaultGuavaCacheManager();
}
@Override
protected void assertIsSame(Cache c1, Cache c2) {
Assert.assertSame(
((GuavaCache)c1).getWrappedCache(),
((GuavaCache)c2).getWrappedCache()
);
}
}

View File

@@ -128,7 +128,7 @@ public class DeactivatedUserITCase
"slart123");
assertNotNull(response);
assertEquals(401, response.getStatus());
assertEquals(403, response.getStatus());
}
//~--- fields ---------------------------------------------------------------

View File

@@ -0,0 +1,235 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.repository;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.util.ThreadContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.SCMContextProvider;
import sonia.scm.Type;
import sonia.scm.cache.GuavaCacheManager;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.AuthorizationCollector;
import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.KeyGenerator;
import sonia.scm.security.RepositoryPermissionResolver;
import sonia.scm.security.SecuritySystem;
import sonia.scm.user.UserTestData;
/**
* Performance test for {@link RepositoryManager#getAll()}.
*
* @see <a href="https://goo.gl/PD1AeM">Issue 781</a>
* @author Sebastian Sdorra
* @since 1.52
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultRepositoryManagerPerfTest {
private static final int REPOSITORY_COUNT = 2000;
private static final String REPOSITORY_TYPE = "perf";
@Mock
private SCMContextProvider contextProvider;
@Mock
private RepositoryDAO repositoryDAO;
@Mock
private PreProcessorUtil preProcessorUtil;
private final ScmConfiguration configuration = new ScmConfiguration();
private final KeyGenerator keyGenerator = new DefaultKeyGenerator();
@Mock
private RepositoryHandler repositoryHandler;
private DefaultRepositoryManager repositoryManager;
@Mock
private SecuritySystem securitySystem;
private AuthorizationCollector authzCollector;
/**
* Setup object under test.
*/
@Before
public void setUpObjectUnderTest(){
when(repositoryHandler.getType()).thenReturn(new Type(REPOSITORY_TYPE, REPOSITORY_TYPE));
Set<RepositoryHandler> handlerSet = ImmutableSet.of(repositoryHandler);
Provider<Set<RepositoryListener>> repositoryListenersProvider = new SetProvider();
Provider<Set<RepositoryHook>> repositoryHooksProvider = new SetProvider();
repositoryManager = new DefaultRepositoryManager(
configuration,
contextProvider,
keyGenerator,
repositoryDAO,
handlerSet,
repositoryListenersProvider,
repositoryHooksProvider,
preProcessorUtil
);
setUpTestRepositories();
GuavaCacheManager cacheManager = new GuavaCacheManager();
authzCollector = new AuthorizationCollector(cacheManager, repositoryDAO, securitySystem, new RepositoryPermissionResolver());
DefaultSecurityManager securityManager = new DefaultSecurityManager(new DummyRealm(authzCollector, cacheManager));
ThreadContext.bind(securityManager);
}
/**
* Tear down test objects.
*/
@After
public void tearDown(){
ThreadContext.unbindSecurityManager();
}
/**
* Start performance test and ensure that the timeout is not reached.
*/
@Test(timeout = 6000l)
public void perfTestGetAll(){
SecurityUtils.getSubject().login(new UsernamePasswordToken("trillian", "secret"));
List<Long> times = new ArrayList<>();
for ( int i=0; i<3; i++ ) {
times.add(benchGetAll());
}
long average = calculateAverage(times);
double value = (double) average / TimeUnit.MILLISECONDS.convert(1, TimeUnit.SECONDS);
// Too bad this functionality is not exposed as a regular method call
System.out.println( String.format("%.4g s", value) );
}
private long calculateAverage(List<Long> times) {
Long sum = 0l;
if(!times.isEmpty()) {
for (Long time : times) {
sum += time;
}
return Math.round(sum.doubleValue() / times.size());
}
return sum;
}
private long benchGetAll(){
Stopwatch sw = Stopwatch.createStarted();
System.out.append("found ").append(String.valueOf(repositoryManager.getAll().size()));
sw.stop();
System.out.append(" in ").println(sw);
return sw.elapsed(TimeUnit.MILLISECONDS);
}
private void setUpTestRepositories() {
Map<String,Repository> repositories = new LinkedHashMap<>();
for ( int i=0; i<REPOSITORY_COUNT; i++ ) {
Repository repository = createTestRepository(i);
repositories.put(repository.getId(), repository);
}
when(repositoryDAO.getAll()).thenReturn(repositories.values());
}
private Repository createTestRepository(int number){
Repository repository = new Repository(keyGenerator.createKey(), REPOSITORY_TYPE, "repo-" + number);
repository.getPermissions().add(new Permission("trillian", PermissionType.READ));
return repository;
}
static class DummyRealm extends AuthorizingRealm {
private final AuthorizationCollector authzCollector;
public DummyRealm(AuthorizationCollector authzCollector, org.apache.shiro.cache.CacheManager cacheManager) {
this.authzCollector = authzCollector;
setCredentialsMatcher(new AllowAllCredentialsMatcher());
setCacheManager(cacheManager);
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
SimplePrincipalCollection spc = new SimplePrincipalCollection(token.getPrincipal(), REPOSITORY_TYPE);
spc.add(UserTestData.createTrillian(), REPOSITORY_TYPE);
return new SimpleAuthenticationInfo(spc, REPOSITORY_TYPE);
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return authzCollector.collect(principals);
}
}
private static class SetProvider implements Provider {
@Override
public Object get() {
return Collections.emptySet();
}
}
}

View File

@@ -0,0 +1,121 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Before;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.user.User;
import sonia.scm.user.UserTestData;
/**
* Unit tests for {@link AdminDetector}.
*
* @author Sebastian Sdorra
* @since 1.52
*/
public class AdminDetectorTest {
private ScmConfiguration configuration;
private AdminDetector detector;
@Before
public void setUpObjectUnderTest(){
configuration = new ScmConfiguration();
detector = new AdminDetector(configuration);
}
/**
* Tests {@link AdminDetector#checkForAuthenticatedAdmin(User, Set)} with configured admin users.
*/
@Test
public void testCheckForAuthenticatedAdminWithConfiguredAdminUsers() {
configuration.setAdminUsers(ImmutableSet.of("slarti"));
User slarti = UserTestData.createSlarti();
slarti.setAdmin(false);
Set<String> groups = ImmutableSet.of();
detector.checkForAuthenticatedAdmin(slarti, groups);
assertTrue(slarti.isAdmin());
}
/**
* Tests {@link AdminDetector#checkForAuthenticatedAdmin(User, Set)} with configured admin group.
*/
@Test
public void testCheckForAuthenticatedAdminWithConfiguredAdminGroup() {
configuration.setAdminGroups(ImmutableSet.of("heartOfGold"));
User slarti = UserTestData.createSlarti();
slarti.setAdmin(false);
Set<String> groups = ImmutableSet.of("heartOfGold");
detector.checkForAuthenticatedAdmin(slarti, groups);
assertTrue(slarti.isAdmin());
}
/**
* Tests {@link AdminDetector#checkForAuthenticatedAdmin(User, Set)} with non matching configuration.
*/
@Test
public void testCheckForAuthenticatedAdminWithNonMatchinConfiguration() {
configuration.setAdminUsers(ImmutableSet.of("slarti"));
configuration.setAdminGroups(ImmutableSet.of("heartOfGold"));
User trillian = UserTestData.createTrillian();
trillian.setAdmin(false);
Set<String> groups = ImmutableSet.of("puzzle42");
detector.checkForAuthenticatedAdmin(trillian, groups);
assertFalse(trillian.isAdmin());
}
/**
* Tests {@link AdminDetector#checkForAuthenticatedAdmin(User, Set)} with user which is already admin.
*/
@Test
public void testCheckForAuthenticatedAdminWithUserWhichIsAlreadyAdmin() {
configuration.setAdminUsers(ImmutableSet.of("slarti"));
configuration.setAdminGroups(ImmutableSet.of("heartOfGold"));
User trillian = UserTestData.createTrillian();
trillian.setAdmin(true);
Set<String> groups = ImmutableSet.of("puzzle42");
detector.checkForAuthenticatedAdmin(trillian, groups);
assertTrue(trillian.isAdmin());
}
}

View File

@@ -0,0 +1,110 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.group.GroupNames;
import sonia.scm.user.User;
import sonia.scm.user.UserTestData;
import sonia.scm.web.security.AuthenticationResult;
/**
* Unit tests for {@link AuthenticationInfoCollector}.
*
* @author Sebastian Sdorra
* @since 1.52
*/
@RunWith(MockitoJUnitRunner.class)
public class AuthenticationInfoCollectorTest {
@Mock
private LocalDatabaseSynchronizer synchronizer;
@Mock
private GroupCollector groupCollector;
@Mock
private CredentialsStore sessionStore;
@InjectMocks
private AuthenticationInfoCollector collector;
/**
* Tests {@link AuthenticationInfoCollector#createAuthenticationInfo(UsernamePasswordToken, AuthenticationResult)}.
*/
@Test
public void testCreateAuthenticationInfo() {
User trillian = UserTestData.createTrillian();
UsernamePasswordToken token = new UsernamePasswordToken(trillian.getId(), "secret");
AuthenticationResult result = new AuthenticationResult(trillian);
Set<String> groups = ImmutableSet.of("puzzle42", "heartOfGold");
when(groupCollector.collectGroups(Mockito.any(AuthenticationResult.class))).thenReturn(groups);
AuthenticationInfo authc = collector.createAuthenticationInfo(token, result);
verify(synchronizer).synchronize(trillian, groups);
verify(sessionStore).store(token);
assertEquals(trillian.getId(), authc.getPrincipals().getPrimaryPrincipal());
assertEquals(trillian, authc.getPrincipals().oneByType(User.class));
assertThat(authc.getPrincipals().oneByType(GroupNames.class), contains(groups.toArray(new String[0])));
}
/**
* Tests {@link AuthenticationInfoCollector#createAuthenticationInfo(UsernamePasswordToken, AuthenticationResult)}
* with disabled user.
*/
@Test(expected = DisabledAccountException.class)
public void testCreateAuthenticationInfoWithDisabledUser() {
User trillian = UserTestData.createTrillian();
trillian.setActive(false);
UsernamePasswordToken token = new UsernamePasswordToken(trillian.getId(), "secret");
AuthenticationResult result = new AuthenticationResult(trillian);
Set<String> groups = ImmutableSet.of("puzzle42", "heartOfGold");
when(groupCollector.collectGroups(Mockito.any(AuthenticationResult.class))).thenReturn(groups);
collector.createAuthenticationInfo(token, result);
}
}

View File

@@ -0,0 +1,256 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.collect.Lists;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Before;
import sonia.scm.HandlerEvent;
import sonia.scm.group.Group;
import sonia.scm.group.GroupEvent;
import sonia.scm.group.GroupModificationEvent;
import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryEvent;
import sonia.scm.repository.RepositoryModificationEvent;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.user.User;
import sonia.scm.user.UserEvent;
import sonia.scm.user.UserModificationEvent;
import sonia.scm.user.UserTestData;
/**
* Unit tests for {@link AuthorizationChangedEventProducer}.
*
* @author Sebastian Sdorra
*/
public class AuthorizationChangedEventProducerTest {
private StoringAuthorizationChangedEventProducer producer;
@Before
public void setUpProducer() {
producer = new StoringAuthorizationChangedEventProducer();
}
/**
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.user.UserEvent)}.
*/
@Test
public void testOnUserEvent()
{
User user = UserTestData.createDent();
producer.onEvent(new UserEvent(user, HandlerEvent.BEFORE_CREATE));
assertEventIsNotFired();
producer.onEvent(new UserEvent(user, HandlerEvent.CREATE));
assertUserEventIsFired("dent");
}
private void assertEventIsNotFired(){
assertNull(producer.event);
}
private void assertUserEventIsFired(String username){
assertNotNull(producer.event);
assertTrue(producer.event.isEveryUserAffected());
assertEquals(username, producer.event.getNameOfAffectedUser());
}
/**
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.user.UserEvent)} with modified user.
*/
@Test
public void testOnUserModificationEvent()
{
User user = UserTestData.createDent();
User userModified = UserTestData.createDent();
userModified.setDisplayName("Super Dent");
producer.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.BEFORE_CREATE));
assertEventIsNotFired();
producer.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.CREATE));
assertEventIsNotFired();
userModified.setAdmin(true);
producer.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.BEFORE_CREATE));
assertEventIsNotFired();
producer.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.CREATE));
assertUserEventIsFired("dent");
}
/**
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)}.
*/
@Test
public void testOnGroupEvent()
{
Group group = new Group("xml", "base");
producer.onEvent(new GroupEvent(group, HandlerEvent.BEFORE_CREATE));
assertEventIsNotFired();
producer.onEvent(new GroupEvent(group, HandlerEvent.CREATE));
assertGlobalEventIsFired();
}
private void assertGlobalEventIsFired(){
assertNotNull(producer.event);
assertFalse(producer.event.isEveryUserAffected());
}
/**
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.group.GroupEvent)} with modified groups.
*/
@Test
public void testOnGroupModificationEvent()
{
Group group = new Group("xml", "base");
Group modifiedGroup = new Group("xml", "base");
producer.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.BEFORE_MODIFY));
assertEventIsNotFired();
producer.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.MODIFY));
assertEventIsNotFired();
modifiedGroup.add("test");
producer.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.MODIFY));
assertGlobalEventIsFired();
}
/**
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.repository.RepositoryEvent)}.
*/
@Test
public void testOnRepositoryEvent()
{
Repository repository = RepositoryTestData.createHeartOfGold();
producer.onEvent(new RepositoryEvent(repository, HandlerEvent.BEFORE_CREATE));
assertEventIsNotFired();
producer.onEvent(new RepositoryEvent(repository, HandlerEvent.CREATE));
assertGlobalEventIsFired();
}
/**
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.repository.RepositoryEvent)} with modified
* repository.
*/
@Test
public void testOnRepositoryModificationEvent()
{
Repository repositoryModified = RepositoryTestData.createHeartOfGold();
repositoryModified.setName("test123");
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
Repository repository = RepositoryTestData.createHeartOfGold();
repository.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.BEFORE_CREATE));
assertEventIsNotFired();
producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
assertEventIsNotFired();
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
assertEventIsNotFired();
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test123")));
producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
assertGlobalEventIsFired();
resetStoredEvent();
repositoryModified.setPermissions(
Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.READ, true))
);
producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
assertGlobalEventIsFired();
resetStoredEvent();
repositoryModified.setPermissions(
Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.WRITE))
);
producer.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
assertGlobalEventIsFired();
}
private void resetStoredEvent(){
producer.event = null;
}
/**
* Tests {@link AuthorizationChangedEventProducer#onEvent(sonia.scm.security.StoredAssignedPermissionEvent)}.
*/
@Test
public void testOnStoredAssignedPermissionEvent()
{
StoredAssignedPermission groupPermission = new StoredAssignedPermission(
"123", new AssignedPermission("_authenticated", true, "repository:read:*")
);
producer.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.BEFORE_CREATE, groupPermission));
assertEventIsNotFired();
producer.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.CREATE, groupPermission));
assertGlobalEventIsFired();
resetStoredEvent();
StoredAssignedPermission userPermission = new StoredAssignedPermission(
"123", new AssignedPermission("trillian", false, "repository:read:*")
);
producer.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.BEFORE_CREATE, userPermission));
assertEventIsNotFired();
resetStoredEvent();
producer.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.CREATE, userPermission));
assertUserEventIsFired("trillian");
}
private static class StoringAuthorizationChangedEventProducer extends AuthorizationChangedEventProducer {
private AuthorizationChangedEvent event;
@Override
protected void sendEvent(AuthorizationChangedEvent event) {
this.event = event;
}
}
}

View File

@@ -54,22 +54,14 @@ import static org.junit.Assert.*;
import org.junit.Rule;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.Filter;
import sonia.scm.HandlerEvent;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.group.Group;
import sonia.scm.group.GroupEvent;
import sonia.scm.group.GroupModificationEvent;
import sonia.scm.group.GroupNames;
import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryEvent;
import sonia.scm.repository.RepositoryModificationEvent;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.user.User;
import sonia.scm.user.UserEvent;
import sonia.scm.user.UserModificationEvent;
import sonia.scm.user.UserTestData;
/**
@@ -111,160 +103,6 @@ public class AuthorizationCollectorTest {
collector = new AuthorizationCollector(cacheManager, repositoryDAO, securitySystem, resolver);
}
/**
* Tests {@link AuthorizationCollector#onEvent(sonia.scm.user.UserEvent)}.
*/
@Test
public void testOnUserEvent()
{
User user = UserTestData.createDent();
collector.onEvent(new UserEvent(user, HandlerEvent.BEFORE_CREATE));
verify(cache, never()).clear();
collector.onEvent(new UserEvent(user, HandlerEvent.CREATE));
verify(cache).removeAll(Mockito.any(Filter.class));
}
/**
* Tests {@link AuthorizationCollector#onEvent(sonia.scm.user.UserEvent)} with modified user.
*/
@Test
public void testOnUserModificationEvent()
{
User user = UserTestData.createDent();
User userModified = UserTestData.createDent();
userModified.setDisplayName("Super Dent");
collector.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.BEFORE_CREATE));
verify(cache, never()).removeAll(Mockito.any(Filter.class));
collector.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.CREATE));
verify(cache, never()).removeAll(Mockito.any(Filter.class));
userModified.setAdmin(true);
collector.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.BEFORE_CREATE));
verify(cache, never()).removeAll(Mockito.any(Filter.class));
collector.onEvent(new UserModificationEvent(userModified, user, HandlerEvent.CREATE));
verify(cache).removeAll(Mockito.any(Filter.class));
}
/**
* Tests {@link AuthorizationCollector#onEvent(sonia.scm.group.GroupEvent)}.
*/
@Test
public void testOnGroupEvent()
{
Group group = new Group("xml", "base");
collector.onEvent(new GroupEvent(group, HandlerEvent.BEFORE_CREATE));
verify(cache, never()).clear();
collector.onEvent(new GroupEvent(group, HandlerEvent.CREATE));
verify(cache).clear();
}
/**
* Tests {@link AuthorizationCollector#onEvent(sonia.scm.group.GroupEvent)} with modified groups.
*/
@Test
public void testOnGroupModificationEvent()
{
Group group = new Group("xml", "base");
Group modifiedGroup = new Group("xml", "base");
collector.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.BEFORE_MODIFY));
verify(cache, never()).clear();
collector.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.MODIFY));
verify(cache, never()).clear();
modifiedGroup.add("test");
collector.onEvent(new GroupModificationEvent(modifiedGroup, group, HandlerEvent.MODIFY));
verify(cache).clear();
}
/**
* Tests {@link AuthorizationCollector#onEvent(sonia.scm.repository.RepositoryEvent)}.
*/
@Test
public void testOnRepositoryEvent()
{
Repository repository = RepositoryTestData.createHeartOfGold();
collector.onEvent(new RepositoryEvent(repository, HandlerEvent.BEFORE_CREATE));
verify(cache, never()).clear();
collector.onEvent(new RepositoryEvent(repository, HandlerEvent.CREATE));
verify(cache).clear();
}
/**
* Tests {@link AuthorizationCollector#onEvent(sonia.scm.repository.RepositoryEvent)} with modified repository.
*/
@Test
public void testOnRepositoryModificationEvent()
{
Repository repositoryModified = RepositoryTestData.createHeartOfGold();
repositoryModified.setName("test123");
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
Repository repository = RepositoryTestData.createHeartOfGold();
repository.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.BEFORE_CREATE));
verify(cache, never()).clear();
collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
verify(cache, never()).clear();
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test")));
collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
verify(cache, never()).clear();
repositoryModified.setPermissions(Lists.newArrayList(new sonia.scm.repository.Permission("test123")));
collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
verify(cache).clear();
repositoryModified.setPermissions(
Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.READ, true))
);
collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
verify(cache, times(2)).clear();
repositoryModified.setPermissions(
Lists.newArrayList(new sonia.scm.repository.Permission("test", PermissionType.WRITE))
);
collector.onEvent(new RepositoryModificationEvent(repositoryModified, repository, HandlerEvent.CREATE));
verify(cache, times(3)).clear();
}
/**
* Tests {@link AuthorizationCollector#onEvent(sonia.scm.security.StoredAssignedPermissionEvent)}.
*/
@Test
public void testOnStoredAssignedPermissionEvent()
{
StoredAssignedPermission groupPermission = new StoredAssignedPermission(
"123", new AssignedPermission("_authenticated", true, "repository:read:*")
);
collector.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.BEFORE_CREATE, groupPermission));
verify(cache, never()).clear();
collector.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.CREATE, groupPermission));
verify(cache).clear();
StoredAssignedPermission userPermission = new StoredAssignedPermission(
"123", new AssignedPermission("trillian", false, "repository:read:*")
);
collector.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.BEFORE_CREATE, userPermission));
verify(cache, never()).removeAll(Mockito.any(Filter.class));
verify(cache).clear();
collector.onEvent(new StoredAssignedPermissionEvent(HandlerEvent.CREATE, userPermission));
verify(cache).removeAll(Mockito.any(Filter.class));
verify(cache).clear();
}
/**
* Tests {@link AuthorizationCollector#collect()} without user role.
*/
@@ -402,8 +240,7 @@ public class AuthorizationCollectorTest {
assertThat(authInfo.getObjectPermissions(), containsInAnyOrder(wp1, wp2));
}
private void authenticate(User user, String group, String... groups)
{
private void authenticate(User user, String group, String... groups) {
SimplePrincipalCollection spc = new SimplePrincipalCollection();
spc.add(user.getName(), "unit");
spc.add(user, "unit");
@@ -412,4 +249,16 @@ public class AuthorizationCollectorTest {
shiro.setSubject(subject);
}
/**
* Tests {@link AuthorizationCollector#invalidateCache(sonia.scm.security.AuthorizationChangedEvent)}.
*/
@Test
public void testInvalidateCache() {
collector.invalidateCache(AuthorizationChangedEvent.createForEveryUser());
verify(cache).clear();
collector.invalidateCache(AuthorizationChangedEvent.createForUser("dent"));
verify(cache).removeAll(Mockito.any(Filter.class));
}
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.inject.Provider;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
/**
* Unit tests for {@link CredentialsStore}.
*
* @author Sebastian Sdorra
* @since 1.52
*/
@RunWith(MockitoJUnitRunner.class)
public class CredentialsStoreTest {
@Mock
private HttpSession session;
private CredentialsStore store;
/**
* Set up object under test.
*/
@Before
public void setUpObjectUnderTest() {
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getSession(true)).thenReturn(session);
Provider<HttpServletRequest> provider = mock(Provider.class);
when(provider.get()).thenReturn(request);
store = new TestableCredentialsStore(provider);
}
/**
* Tests {@link CredentialsStore#store(org.apache.shiro.authc.UsernamePasswordToken)}.
*/
@Test
public void testStore() {
store.store(new UsernamePasswordToken("trillian", "trillian123"));
verify(session).setAttribute(CredentialsStore.SCM_CREDENTIALS, "x_trillian:trillian123");
}
private static class TestableCredentialsStore extends CredentialsStore {
public TestableCredentialsStore(Provider<HttpServletRequest> requestProvider) {
super(requestProvider);
}
@Override
protected String encrypt(String credentials) {
return "x_".concat(credentials);
}
}
}

View File

@@ -0,0 +1,107 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import jdk.nashorn.internal.ir.annotations.Immutable;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.*;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.user.UserTestData;
import sonia.scm.web.security.AuthenticationResult;
/**
* Unit tests for {@link GroupCollector}.
*
* @author Sebastian Sdorra
* @since 1.52
*/
@RunWith(MockitoJUnitRunner.class)
public class GroupCollectorTest {
@Mock
private GroupManager groupManager;
@InjectMocks
private GroupCollector collector;
/**
* Tests {@link GroupCollector#collectGroups(AuthenticationResult)} without groups from authenticator.
*/
@Test
public void testCollectGroupsWithoutAuthenticatorGroups() {
Set<String> groups = collector.collectGroups(new AuthenticationResult(UserTestData.createSlarti()));
assertThat(groups, containsInAnyOrder("_authenticated"));
}
/**
* Tests {@link GroupCollector#collectGroups(AuthenticationResult)} with groups from authenticator.
*/
@Test
public void testCollectGroupsWithGroupsFromAuthenticator() {
Set<String> authGroups = ImmutableSet.of("puzzle42");
Set<String> groups = collector.collectGroups(new AuthenticationResult(UserTestData.createSlarti(), authGroups));
assertThat(groups, containsInAnyOrder("_authenticated", "puzzle42"));
}
/**
* Tests {@link GroupCollector#collectGroups(AuthenticationResult)} with groups from db.
*/
@Test
public void testCollectGroupsWithGroupsFromDB() {
Set<Group> dbGroups = ImmutableSet.of(new Group("test", "puzzle42"));
when(groupManager.getGroupsForMember("slarti")).thenReturn(dbGroups);
Set<String> groups = collector.collectGroups(new AuthenticationResult(UserTestData.createSlarti()));
assertThat(groups, containsInAnyOrder("_authenticated", "puzzle42"));
}
/**
* Tests {@link GroupCollector#collectGroups(AuthenticationResult)} with groups from db.
*/
@Test
public void testCollectGroupsWithGroupsFromDBAndAuthenticator() {
Set<Group> dbGroups = ImmutableSet.of(new Group("test", "puzzle42"));
Set<String> authGroups = ImmutableSet.of("heartOfGold");
when(groupManager.getGroupsForMember("slarti")).thenReturn(dbGroups);
Set<String> groups = collector.collectGroups(new AuthenticationResult(UserTestData.createSlarti(), authGroups));
assertThat(groups, containsInAnyOrder("_authenticated", "puzzle42", "heartOfGold"));
}
}

View File

@@ -0,0 +1,106 @@
/**
* Copyright (c) 2014, Sebastian Sdorra
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of SCM-Manager; nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://bitbucket.org/sdorra/scm-manager
*
*/
package sonia.scm.security;
import com.google.common.collect.ImmutableSet;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.user.User;
import sonia.scm.user.UserDAO;
import sonia.scm.user.UserManager;
import sonia.scm.user.UserTestData;
/**
* Unit tests for {@link LocalDatabaseSynchronizer}.
*
* @author Sebastian Sdorra
* @since 1.52
*/
@RunWith(MockitoJUnitRunner.class)
public class LocalDatabaseSynchronizerTest {
@Mock
private AdminDetector adminSelector;
@Mock
private UserManager userManager;
@Mock
private UserDAO userDAO;
@InjectMocks
private LocalDatabaseSynchronizer synchronizer;
/**
* Tests {@link LocalDatabaseSynchronizer#synchronize(User, java.util.Set)}.
*/
@Test
public void testSynchronizeWithoutDBUser() {
User trillian = UserTestData.createTrillian();
trillian.setType("local");
synchronizer.synchronize(trillian, ImmutableSet.<String>of());
verify(userDAO).add(trillian);
}
/**
* Tests {@link LocalDatabaseSynchronizer#synchronize(sonia.scm.user.User, java.util.Set)}.
*/
@Test
public void testSynchronize() {
User trillian = UserTestData.createTrillian();
trillian.setDisplayName("Trici");
trillian.setType("local");
trillian.setAdmin(false);
trillian.setActive(true);
User dbTrillian = UserTestData.createTrillian();
dbTrillian.setType("local");
dbTrillian.setAdmin(true);
dbTrillian.setActive(false);
when(userDAO.get(trillian.getId())).thenReturn(dbTrillian);
synchronizer.synchronize(trillian, ImmutableSet.<String>of());
assertTrue(trillian.isAdmin());
assertFalse(trillian.isActive());
verify(userDAO).modify(trillian);
}
}

View File

@@ -54,8 +54,6 @@ import org.junit.Test;
import org.mockito.Mockito;
import sonia.scm.cache.CacheManager;
import sonia.scm.cache.MapCacheManager;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
@@ -89,6 +87,7 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import sonia.scm.cache.GuavaCacheManager;
/**
*
@@ -477,9 +476,23 @@ public class ScmRealmTest
Collections.EMPTY_LIST
);
CacheManager cacheManager = new MapCacheManager();
GuavaCacheManager cacheManager = new GuavaCacheManager();
AuthorizationCollector collector = new AuthorizationCollector(
AdminDetector adminSelector = new AdminDetector(new ScmConfiguration());
LocalDatabaseSynchronizer synchronizer = new LocalDatabaseSynchronizer(
adminSelector, userManager, userDAO
);
GroupCollector groupCollector = new GroupCollector(groupManager);
CredentialsStore sessionStore = new CredentialsStore(requestProvider);
AuthenticationInfoCollector authcCollector = new AuthenticationInfoCollector(
synchronizer,
groupCollector,
sessionStore
);
AuthorizationCollector authzCollector = new AuthorizationCollector(
cacheManager,
repositoryDAO,
securitySystem,
@@ -502,17 +515,11 @@ public class ScmRealmTest
};
return new ScmRealm(
new ScmConfiguration(),
cacheManager,
new AuthenticatorFacade(authManager, requestProvider, responseProvider),
dummyLoginAttemptHandler,
collector,
// cacheManager,
userManager,
groupManager,
userDAO,
authManager,
null,
requestProvider,
responseProvider
authcCollector,
authzCollector
);
//J+
}