mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-11 07:55:47 +01:00
merge with branch issue-781
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -166,6 +166,7 @@ import sonia.scm.net.ahc.JsonContentTransformer;
|
|||||||
import sonia.scm.net.ahc.XmlContentTransformer;
|
import sonia.scm.net.ahc.XmlContentTransformer;
|
||||||
import sonia.scm.schedule.QuartzScheduler;
|
import sonia.scm.schedule.QuartzScheduler;
|
||||||
import sonia.scm.schedule.Scheduler;
|
import sonia.scm.schedule.Scheduler;
|
||||||
|
import sonia.scm.security.AuthorizationChangedEventProducer;
|
||||||
import sonia.scm.security.XsrfProtectionFilter;
|
import sonia.scm.security.XsrfProtectionFilter;
|
||||||
import sonia.scm.web.UserAgentParser;
|
import sonia.scm.web.UserAgentParser;
|
||||||
|
|
||||||
@@ -300,6 +301,7 @@ public class ScmServletModule extends ServletModule
|
|||||||
pluginLoader.processExtensions(binder());
|
pluginLoader.processExtensions(binder());
|
||||||
|
|
||||||
// bind security stuff
|
// bind security stuff
|
||||||
|
bind(AuthorizationChangedEventProducer.class);
|
||||||
bind(PermissionResolver.class, RepositoryPermissionResolver.class);
|
bind(PermissionResolver.class, RepositoryPermissionResolver.class);
|
||||||
bind(AuthenticationManager.class, ChainAuthenticatonManager.class);
|
bind(AuthenticationManager.class, ChainAuthenticatonManager.class);
|
||||||
bind(SecurityContext.class).to(BasicSecurityContext.class);
|
bind(SecurityContext.class).to(BasicSecurityContext.class);
|
||||||
@@ -310,6 +312,7 @@ public class ScmServletModule extends ServletModule
|
|||||||
|
|
||||||
// bind cache
|
// bind cache
|
||||||
bind(CacheManager.class, GuavaCacheManager.class);
|
bind(CacheManager.class, GuavaCacheManager.class);
|
||||||
|
bind(org.apache.shiro.cache.CacheManager.class, GuavaCacheManager.class);
|
||||||
|
|
||||||
// bind dao
|
// bind dao
|
||||||
bind(GroupDAO.class, XmlGroupDAO.class);
|
bind(GroupDAO.class, XmlGroupDAO.class);
|
||||||
@@ -386,8 +389,7 @@ public class ScmServletModule extends ServletModule
|
|||||||
filter(PATTERN_ALL).through(BaseUrlFilter.class);
|
filter(PATTERN_ALL).through(BaseUrlFilter.class);
|
||||||
filter(PATTERN_ALL).through(AutoLoginFilter.class);
|
filter(PATTERN_ALL).through(AutoLoginFilter.class);
|
||||||
filterRegex(RESOURCE_REGEX).through(GZipFilter.class);
|
filterRegex(RESOURCE_REGEX).through(GZipFilter.class);
|
||||||
filter(PATTERN_RESTAPI,
|
filter(PATTERN_RESTAPI, PATTERN_DEBUG).through(ApiBasicAuthenticationFilter.class);
|
||||||
PATTERN_DEBUG).through(ApiBasicAuthenticationFilter.class);
|
|
||||||
filter(PATTERN_RESTAPI, PATTERN_DEBUG).through(SecurityFilter.class);
|
filter(PATTERN_RESTAPI, PATTERN_DEBUG).through(SecurityFilter.class);
|
||||||
filter(PATTERN_CONFIG, PATTERN_ADMIN).through(AdminSecurityFilter.class);
|
filter(PATTERN_CONFIG, PATTERN_ADMIN).through(AdminSecurityFilter.class);
|
||||||
|
|
||||||
@@ -434,11 +436,7 @@ public class ScmServletModule extends ServletModule
|
|||||||
UriExtensionsConfig.class.getName());
|
UriExtensionsConfig.class.getName());
|
||||||
|
|
||||||
String restPath = getRestPackages();
|
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);
|
params.put(PackagesResourceConfig.PROPERTY_PACKAGES, restPath);
|
||||||
serve(PATTERN_RESTAPI).with(GuiceContainer.class, params);
|
serve(PATTERN_RESTAPI).with(GuiceContainer.class, params);
|
||||||
|
|||||||
113
scm-webapp/src/main/java/sonia/scm/cache/GuavaBaseCache.java
vendored
Normal file
113
scm-webapp/src/main/java/sonia/scm/cache/GuavaBaseCache.java
vendored
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,18 +34,6 @@ package sonia.scm.cache;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- 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
|
* @author Sebastian Sdorra
|
||||||
@@ -53,185 +41,22 @@ import java.util.Set;
|
|||||||
* @param <K>
|
* @param <K>
|
||||||
* @param <V>
|
* @param <V>
|
||||||
*/
|
*/
|
||||||
public class GuavaCache<K, V> implements Cache<K, V>
|
public class GuavaCache<K, V> extends GuavaBaseCache<K, V> implements Cache<K, V> {
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
GuavaCache(com.google.common.cache.Cache<K, V> cache, CopyStrategy copyStrategy, String name) {
|
||||||
* the logger for GuavaCache
|
super(cache, copyStrategy, name);
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(GuavaCache.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param configuration
|
|
||||||
*/
|
|
||||||
public GuavaCache(GuavaNamedCacheConfiguration configuration)
|
|
||||||
{
|
|
||||||
this(configuration, configuration.getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
@Override
|
||||||
public void clear()
|
public void put(K key, V value) {
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
cache.put(key, copyStrategy.copyOnWrite(value));
|
cache.put(key, copyStrategy.copyOnWrite(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean remove(K key)
|
public boolean remove(K key) {
|
||||||
{
|
|
||||||
cache.invalidate(key);
|
cache.invalidate(key);
|
||||||
|
|
||||||
return true;
|
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,66 +47,52 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import java.util.Map;
|
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
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class GuavaCacheManager implements CacheManager
|
public class GuavaCacheManager implements CacheManager, org.apache.shiro.cache.CacheManager
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the logger for GuavaCacheManager
|
* the logger for GuavaCacheManager
|
||||||
*/
|
*/
|
||||||
private static final Logger logger =
|
private static final Logger logger = LoggerFactory.getLogger(GuavaCacheManager.class);
|
||||||
LoggerFactory.getLogger(GuavaCacheManager.class);
|
|
||||||
|
private volatile Map<String, CacheWithConfiguration> cacheMap = Maps.newHashMap();
|
||||||
|
|
||||||
|
private GuavaCacheConfiguration defaultConfiguration;
|
||||||
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
/**
|
public GuavaCacheManager() {
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public GuavaCacheManager()
|
|
||||||
{
|
|
||||||
this(GuavaCacheConfigurationReader.read());
|
this(GuavaCacheConfigurationReader.read());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param config
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected GuavaCacheManager(GuavaCacheManagerConfiguration config)
|
protected GuavaCacheManager(GuavaCacheManagerConfiguration config) {
|
||||||
{
|
|
||||||
defaultConfiguration = config.getDefaultCache();
|
defaultConfiguration = config.getDefaultCache();
|
||||||
|
|
||||||
for (GuavaNamedCacheConfiguration ncc : config.getCaches())
|
for (GuavaNamedCacheConfiguration ncc : config.getCaches()) {
|
||||||
{
|
logger.debug("create cache {} from configured configuration {}", ncc.getName(), ncc);
|
||||||
logger.debug("create cache {} from configured configuration {}",
|
cacheMap.put(ncc.getName(), new CacheWithConfiguration(
|
||||||
ncc.getName(), ncc);
|
GuavaCaches.create(defaultConfiguration, ncc.getName()),
|
||||||
cacheMap.put(ncc.getName(), new GuavaCache(ncc));
|
defaultConfiguration)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException
|
public void close() throws IOException {
|
||||||
{
|
|
||||||
logger.info("close guava cache manager");
|
logger.info("close guava cache manager");
|
||||||
|
|
||||||
for (Cache c : cacheMap.values())
|
for (CacheWithConfiguration c : cacheMap.values()) {
|
||||||
{
|
c.cache.invalidateAll();
|
||||||
c.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cacheMap.clear();
|
cacheMap.clear();
|
||||||
@@ -114,43 +100,44 @@ public class GuavaCacheManager implements CacheManager
|
|||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
private synchronized <K, V> CacheWithConfiguration<K, V> getCacheWithConfiguration(String name) {
|
||||||
* 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)
|
|
||||||
{
|
|
||||||
logger.trace("try to retrieve cache {}", 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(
|
logger.debug(
|
||||||
"cache {} does not exists, creating a new instance from default configuration: {}",
|
"cache {} does not exists, creating a new instance from default configuration: {}",
|
||||||
name, defaultConfiguration);
|
name, defaultConfiguration
|
||||||
cache = new GuavaCache<K, V>(defaultConfiguration, name);
|
);
|
||||||
|
cache = new CacheWithConfiguration(GuavaCaches.create(defaultConfiguration, name), defaultConfiguration);
|
||||||
cacheMap.put(name, cache);
|
cacheMap.put(name, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 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 */
|
@Override
|
||||||
private volatile Map<String, GuavaCache> cacheMap = Maps.newHashMap();
|
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 static class CacheWithConfiguration<K,V> {
|
||||||
private GuavaCacheConfiguration defaultConfiguration;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
scm-webapp/src/main/java/sonia/scm/cache/GuavaSecurityCache.java
vendored
Normal file
83
scm-webapp/src/main/java/sonia/scm/cache/GuavaSecurityCache.java
vendored
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
105
scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java
Normal file
105
scm-webapp/src/main/java/sonia/scm/security/AdminDetector.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -57,14 +57,11 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import sonia.scm.cache.Cache;
|
import sonia.scm.cache.Cache;
|
||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
import sonia.scm.group.GroupEvent;
|
|
||||||
import sonia.scm.group.GroupNames;
|
import sonia.scm.group.GroupNames;
|
||||||
import sonia.scm.repository.PermissionType;
|
import sonia.scm.repository.PermissionType;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryDAO;
|
import sonia.scm.repository.RepositoryDAO;
|
||||||
import sonia.scm.repository.RepositoryEvent;
|
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserEvent;
|
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -72,10 +69,6 @@ import sonia.scm.util.Util;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import sonia.scm.Filter;
|
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,
|
RepositoryDAO repositoryDAO, SecuritySystem securitySystem,
|
||||||
PermissionResolver resolver)
|
PermissionResolver resolver)
|
||||||
{
|
{
|
||||||
this.cache = cacheManager.getCache(CacheKey.class, AuthorizationInfo.class,
|
this.cache = cacheManager.getCache(CacheKey.class, AuthorizationInfo.class, CACHE_NAME);
|
||||||
CACHE_NAME);
|
|
||||||
this.repositoryDAO = repositoryDAO;
|
this.repositoryDAO = repositoryDAO;
|
||||||
this.securitySystem = securitySystem;
|
this.securitySystem = securitySystem;
|
||||||
this.resolver = resolver;
|
this.resolver = resolver;
|
||||||
@@ -146,194 +138,14 @@ public class AuthorizationCollector
|
|||||||
return authorizationInfo;
|
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
|
* Method description
|
||||||
*
|
*
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param principals
|
* @param principals
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
AuthorizationInfo collect(PrincipalCollection principals)
|
public AuthorizationInfo collect(PrincipalCollection principals)
|
||||||
{
|
{
|
||||||
Preconditions.checkNotNull(principals, "principals parameter is required");
|
Preconditions.checkNotNull(principals, "principals parameter is required");
|
||||||
|
|
||||||
@@ -568,6 +380,30 @@ public class AuthorizationCollector
|
|||||||
//J+
|
//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 --------------------------------------------------------
|
//~--- inner classes --------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
123
scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java
Normal file
123
scm-webapp/src/main/java/sonia/scm/security/GroupCollector.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -35,531 +35,137 @@ package sonia.scm.security;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.google.common.collect.Sets;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Provider;
|
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
import org.apache.shiro.authc.AccountException;
|
import org.apache.shiro.authc.AccountException;
|
||||||
import org.apache.shiro.authc.AuthenticationException;
|
import org.apache.shiro.authc.AuthenticationException;
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
import org.apache.shiro.authc.AuthenticationInfo;
|
||||||
import org.apache.shiro.authc.AuthenticationToken;
|
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.UnknownAccountException;
|
||||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||||
import org.apache.shiro.authc.pam.UnsupportedTokenException;
|
import org.apache.shiro.authc.pam.UnsupportedTokenException;
|
||||||
import org.apache.shiro.authz.AuthorizationInfo;
|
import org.apache.shiro.authz.AuthorizationInfo;
|
||||||
|
import org.apache.shiro.cache.CacheManager;
|
||||||
import org.apache.shiro.realm.AuthorizingRealm;
|
import org.apache.shiro.realm.AuthorizingRealm;
|
||||||
import org.apache.shiro.subject.PrincipalCollection;
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.AuthenticationResult;
|
||||||
import sonia.scm.web.security.AuthenticationState;
|
import sonia.scm.web.security.AuthenticationState;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- 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
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ScmRealm extends AuthorizingRealm
|
public class ScmRealm extends AuthorizingRealm {
|
||||||
{
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String NAME = "scm";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the logger for ScmRealm
|
* the logger for ScmRealm
|
||||||
*/
|
*/
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ScmRealm.class);
|
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 cacheManager cache manager
|
||||||
* @param loginAttemptHandler
|
* @param authenticator authenticator facade
|
||||||
* @param collector
|
* @param loginAttemptHandler login attempt handler
|
||||||
* @param userManager
|
* @param authcCollector authentication info collector
|
||||||
* @param groupManager
|
* @param authzCollector authorization collector
|
||||||
* @param userDAO
|
|
||||||
* @param authenticator
|
|
||||||
* @param manager
|
|
||||||
* @param requestProvider
|
|
||||||
* @param responseProvider
|
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public ScmRealm(ScmConfiguration configuration,
|
public ScmRealm(CacheManager cacheManager,
|
||||||
LoginAttemptHandler loginAttemptHandler, AuthorizationCollector collector,
|
AuthenticatorFacade authenticator, LoginAttemptHandler loginAttemptHandler,
|
||||||
UserManager userManager, GroupManager groupManager, UserDAO userDAO,
|
AuthenticationInfoCollector authcCollector, AuthorizationCollector authzCollector) {
|
||||||
AuthenticationManager authenticator, RepositoryManager manager,
|
super(cacheManager);
|
||||||
Provider<HttpServletRequest> requestProvider,
|
|
||||||
Provider<HttpServletResponse> responseProvider)
|
|
||||||
{
|
|
||||||
this.configuration = configuration;
|
|
||||||
this.loginAttemptHandler = loginAttemptHandler;
|
|
||||||
this.collector = collector;
|
|
||||||
this.userManager = userManager;
|
|
||||||
this.groupManager = groupManager;
|
|
||||||
this.userDAO = userDAO;
|
|
||||||
this.authenticator = authenticator;
|
this.authenticator = authenticator;
|
||||||
this.requestProvider = requestProvider;
|
this.loginAttemptHandler = loginAttemptHandler;
|
||||||
this.responseProvider = responseProvider;
|
this.authcCollector = authcCollector;
|
||||||
|
this.authzCollector = authzCollector;
|
||||||
|
|
||||||
// set token class
|
// set token class
|
||||||
setAuthenticationTokenClass(UsernamePasswordToken.class);
|
setAuthenticationTokenClass(UsernamePasswordToken.class);
|
||||||
|
|
||||||
// use own custom caching
|
|
||||||
setCachingEnabled(false);
|
|
||||||
setAuthenticationCachingEnabled(false);
|
|
||||||
setAuthorizationCachingEnabled(false);
|
|
||||||
|
|
||||||
// set components
|
// set components
|
||||||
setPermissionResolver(new RepositoryPermissionResolver());
|
setPermissionResolver(new RepositoryPermissionResolver());
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param authToken
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*
|
|
||||||
* @throws AuthenticationException
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
protected AuthenticationInfo doGetAuthenticationInfo(
|
protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
|
||||||
AuthenticationToken authToken)
|
return principals.getPrimaryPrincipal();
|
||||||
throws AuthenticationException
|
|
||||||
{
|
|
||||||
if (!(authToken instanceof UsernamePasswordToken))
|
|
||||||
{
|
|
||||||
throw new UnsupportedTokenException("ScmAuthenticationToken is required");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
if (isSuccessful(result)) {
|
||||||
AuthenticationResult result =
|
loginAttemptHandler.onSuccessfulAuthentication(token, result);
|
||||||
authenticator.authenticate(requestProvider.get(), responseProvider.get(),
|
return authcCollector.createAuthenticationInfo(token, result);
|
||||||
token.getUsername(), new String(token.getPassword()));
|
} else if (isAccountNotFound(result)) {
|
||||||
|
throw new UnknownAccountException("unknown account ".concat(token.getUsername()));
|
||||||
if ((result != null) && (AuthenticationState.SUCCESS == result.getState()))
|
} else {
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
loginAttemptHandler.onUnsuccessfulAuthentication(authToken, result);
|
loginAttemptHandler.onUnsuccessfulAuthentication(authToken, result);
|
||||||
|
|
||||||
throw new AccountException("authentication failed");
|
throw new AccountException("authentication failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private UsernamePasswordToken castToken(AuthenticationToken token) {
|
||||||
* Method description
|
if (!(token instanceof UsernamePasswordToken)) {
|
||||||
*
|
throw new UnsupportedTokenException("UsernamePasswordToken is required");
|
||||||
*
|
}
|
||||||
* @param principals
|
return (UsernamePasswordToken) token;
|
||||||
*
|
}
|
||||||
* @return
|
|
||||||
*/
|
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
|
@Override
|
||||||
protected AuthorizationInfo doGetAuthorizationInfo(
|
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
|
||||||
PrincipalCollection principals)
|
return authzCollector.collect(principals);
|
||||||
{
|
|
||||||
return collector.collect(principals);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Subscribe
|
||||||
* Method description
|
public void invalidateCache(AuthorizationChangedEvent event) {
|
||||||
*
|
if (event.isEveryUserAffected()) {
|
||||||
*
|
invalidateUserCache(event.getNameOfAffectedUser());
|
||||||
* @param request
|
} else {
|
||||||
* @param password
|
invalidateCache();
|
||||||
* @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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void invalidateUserCache(final String username) {
|
||||||
* Method description
|
logger.info("invalidate cache for user {}, because of a received authorization event", username);
|
||||||
*
|
getAuthorizationCache().remove(username);
|
||||||
*
|
|
||||||
* @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);
|
private void invalidateCache() {
|
||||||
|
logger.info("invalidate cache, because of a received authorization event");
|
||||||
|
getAuthorizationCache().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,8 +159,7 @@ public abstract class CacheManagerTestBase<C extends Cache>
|
|||||||
* @param c1
|
* @param c1
|
||||||
* @param c2
|
* @param c2
|
||||||
*/
|
*/
|
||||||
protected void assertIsSame(Cache<String, String> c1,
|
protected void assertIsSame(Cache<String, String> c1, Cache<String, String> c2)
|
||||||
Cache<String, String> c2)
|
|
||||||
{
|
{
|
||||||
assertSame(c1, c2);
|
assertSame(c1, c2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
|
|
||||||
package sonia.scm.cache;
|
package sonia.scm.cache;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -50,4 +52,14 @@ public class GuavaCacheManagerTest extends CacheManagerTestBase
|
|||||||
{
|
{
|
||||||
return CacheTestUtil.createDefaultGuavaCacheManager();
|
return CacheTestUtil.createDefaultGuavaCacheManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void assertIsSame(Cache c1, Cache c2) {
|
||||||
|
Assert.assertSame(
|
||||||
|
((GuavaCache)c1).getWrappedCache(),
|
||||||
|
((GuavaCache)c2).getWrappedCache()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ public class DeactivatedUserITCase
|
|||||||
"slart123");
|
"slart123");
|
||||||
|
|
||||||
assertNotNull(response);
|
assertNotNull(response);
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(403, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -54,22 +54,14 @@ import static org.junit.Assert.*;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.mockito.runners.MockitoJUnitRunner;
|
import org.mockito.runners.MockitoJUnitRunner;
|
||||||
import sonia.scm.Filter;
|
import sonia.scm.Filter;
|
||||||
import sonia.scm.HandlerEvent;
|
|
||||||
import sonia.scm.cache.Cache;
|
import sonia.scm.cache.Cache;
|
||||||
import sonia.scm.cache.CacheManager;
|
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.group.GroupNames;
|
||||||
import sonia.scm.repository.PermissionType;
|
import sonia.scm.repository.PermissionType;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryDAO;
|
import sonia.scm.repository.RepositoryDAO;
|
||||||
import sonia.scm.repository.RepositoryEvent;
|
|
||||||
import sonia.scm.repository.RepositoryModificationEvent;
|
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserEvent;
|
|
||||||
import sonia.scm.user.UserModificationEvent;
|
|
||||||
import sonia.scm.user.UserTestData;
|
import sonia.scm.user.UserTestData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,160 +103,6 @@ public class AuthorizationCollectorTest {
|
|||||||
collector = new AuthorizationCollector(cacheManager, repositoryDAO, securitySystem, resolver);
|
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.
|
* Tests {@link AuthorizationCollector#collect()} without user role.
|
||||||
*/
|
*/
|
||||||
@@ -402,8 +240,7 @@ public class AuthorizationCollectorTest {
|
|||||||
assertThat(authInfo.getObjectPermissions(), containsInAnyOrder(wp1, wp2));
|
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();
|
SimplePrincipalCollection spc = new SimplePrincipalCollection();
|
||||||
spc.add(user.getName(), "unit");
|
spc.add(user.getName(), "unit");
|
||||||
spc.add(user, "unit");
|
spc.add(user, "unit");
|
||||||
@@ -412,4 +249,16 @@ public class AuthorizationCollectorTest {
|
|||||||
shiro.setSubject(subject);
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -54,8 +54,6 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
import sonia.scm.cache.CacheManager;
|
|
||||||
import sonia.scm.cache.MapCacheManager;
|
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.group.GroupManager;
|
import sonia.scm.group.GroupManager;
|
||||||
@@ -89,6 +87,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
import sonia.scm.cache.GuavaCacheManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -477,9 +476,23 @@ public class ScmRealmTest
|
|||||||
Collections.EMPTY_LIST
|
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,
|
cacheManager,
|
||||||
repositoryDAO,
|
repositoryDAO,
|
||||||
securitySystem,
|
securitySystem,
|
||||||
@@ -502,17 +515,11 @@ public class ScmRealmTest
|
|||||||
};
|
};
|
||||||
|
|
||||||
return new ScmRealm(
|
return new ScmRealm(
|
||||||
new ScmConfiguration(),
|
cacheManager,
|
||||||
|
new AuthenticatorFacade(authManager, requestProvider, responseProvider),
|
||||||
dummyLoginAttemptHandler,
|
dummyLoginAttemptHandler,
|
||||||
collector,
|
authcCollector,
|
||||||
// cacheManager,
|
authzCollector
|
||||||
userManager,
|
|
||||||
groupManager,
|
|
||||||
userDAO,
|
|
||||||
authManager,
|
|
||||||
null,
|
|
||||||
requestProvider,
|
|
||||||
responseProvider
|
|
||||||
);
|
);
|
||||||
//J+
|
//J+
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user