improve caching of roles and permissions

This commit is contained in:
Sebastian Sdorra
2013-05-29 20:52:48 +02:00
parent 235b3322fe
commit c8d98fadb1
6 changed files with 102 additions and 147 deletions

View File

@@ -59,7 +59,7 @@ import sonia.scm.ScmState;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.GroupNames; import sonia.scm.group.GroupNames;
import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManager;
import sonia.scm.security.PermissionCollector; import sonia.scm.security.AuthorizationCollector;
import sonia.scm.security.PermissionDescriptor; import sonia.scm.security.PermissionDescriptor;
import sonia.scm.security.Role; import sonia.scm.security.Role;
import sonia.scm.security.SecuritySystem; import sonia.scm.security.SecuritySystem;
@@ -121,7 +121,7 @@ public class AuthenticationResource
public AuthenticationResource(SCMContextProvider contextProvider, public AuthenticationResource(SCMContextProvider contextProvider,
ScmConfiguration configuration, RepositoryManager repositoryManger, ScmConfiguration configuration, RepositoryManager repositoryManger,
UserManager userManager, SecuritySystem securitySystem, UserManager userManager, SecuritySystem securitySystem,
PermissionCollector collector) AuthorizationCollector collector)
{ {
this.contextProvider = contextProvider; this.contextProvider = contextProvider;
this.configuration = configuration; this.configuration = configuration;
@@ -338,7 +338,7 @@ public class AuthenticationResource
Builder<String> builder = ImmutableList.builder(); Builder<String> builder = ImmutableList.builder();
for (Permission p : permissionCollector.collect()) for (Permission p : permissionCollector.collect().getObjectPermissions())
{ {
if (p instanceof StringablePermission) if (p instanceof StringablePermission)
{ {
@@ -380,7 +380,7 @@ public class AuthenticationResource
private SCMContextProvider contextProvider; private SCMContextProvider contextProvider;
/** Field description */ /** Field description */
private PermissionCollector permissionCollector; private AuthorizationCollector permissionCollector;
/** Field description */ /** Field description */
private RepositoryManager repositoryManger; private RepositoryManager repositoryManger;

View File

@@ -33,15 +33,19 @@ package sonia.scm.security;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.permission.PermissionResolver; import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
@@ -52,36 +56,34 @@ 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.GroupNames; import sonia.scm.group.GroupNames;
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.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 ------------------------------------------------------------
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*/ */
@Singleton @Singleton
public class PermissionCollector public class AuthorizationCollector
{ {
// CACHE Authorization info ???
/** Field description */ /** Field description */
private static final String NAME = "sonia.cache.permissions"; private static final String CACHE_NAME = "sonia.cache.authorizing";
/** /**
* the logger for PermissionCollector * the logger for AuthorizationCollector
*/ */
private static final Logger logger = private static final Logger logger =
LoggerFactory.getLogger(PermissionCollector.class); LoggerFactory.getLogger(AuthorizationCollector.class);
//~--- constructors --------------------------------------------------------- //~--- constructors ---------------------------------------------------------
@@ -96,11 +98,12 @@ public class PermissionCollector
* @param resolver * @param resolver
*/ */
@Inject @Inject
public PermissionCollector(CacheManager cacheManager, public AuthorizationCollector(CacheManager cacheManager,
RepositoryDAO repositoryDAO, SecuritySystem securitySystem, RepositoryDAO repositoryDAO, SecuritySystem securitySystem,
PermissionResolver resolver) PermissionResolver resolver)
{ {
this.cache = cacheManager.getCache(String.class, List.class, NAME); this.cache = cacheManager.getCache(String.class, AuthorizationInfo.class,
CACHE_NAME);
this.repositoryDAO = repositoryDAO; this.repositoryDAO = repositoryDAO;
this.securitySystem = securitySystem; this.securitySystem = securitySystem;
this.resolver = resolver; this.resolver = resolver;
@@ -114,24 +117,45 @@ public class PermissionCollector
* *
* @return * @return
*/ */
public List<Permission> collect() public AuthorizationInfo collect()
{ {
List<Permission> permissions; AuthorizationInfo authorizationInfo;
Subject subject = SecurityUtils.getSubject(); Subject subject = SecurityUtils.getSubject();
if (subject.hasRole(Role.USER)) if (subject.hasRole(Role.USER))
{ {
PrincipalCollection pc = subject.getPrincipals(); authorizationInfo = collect(subject.getPrincipals());
permissions = collect(pc.oneByType(User.class),
pc.oneByType(GroupNames.class));
} }
else else
{ {
permissions = Collections.EMPTY_LIST; authorizationInfo = new SimpleAuthorizationInfo();
} }
return permissions; return authorizationInfo;
}
/**
* Method description
*
*
* @param event
*/
@Subscribe
public void onEvent(UserEvent event)
{
if (event.getEventType().isPost())
{
User user = event.getItem();
if (logger.isDebugEnabled())
{
logger.debug(
"clear cache of user {}, because user properties have changed",
user.getName());
}
cache.remove(user.getId());
}
} }
/** /**
@@ -183,19 +207,39 @@ public class PermissionCollector
* @param user * @param user
* @param groups * @param groups
* *
* @param principals
*
* @return * @return
*/ */
List<Permission> collect(User user, GroupNames groups) AuthorizationInfo collect(PrincipalCollection principals)
{ {
List<Permission> permissions = cache.get(user.getName()); Preconditions.checkNotNull(principals, "principals parameter is required");
if (permissions == null) User user = principals.oneByType(User.class);
Preconditions.checkNotNull(user, "no user found in principal collection");
AuthorizationInfo info = cache.get(user.getId());
if (info == null)
{ {
permissions = doCollect(user, groups); if (logger.isTraceEnabled())
cache.put(user.getName(), permissions); {
logger.trace("collect AuthorizationInfo for user {}", user.getName());
}
GroupNames groupNames = principals.oneByType(GroupNames.class);
info = createAuthorizationInfo(user, groupNames);
cache.put(user.getId(), info);
}
else if (logger.isTraceEnabled())
{
logger.trace("retrieve AuthorizationInfo for user {} from cache",
user.getName());
} }
return permissions; return info;
} }
/** /**
@@ -324,31 +368,34 @@ public class PermissionCollector
* *
* @return * @return
*/ */
private List<Permission> doCollect(User user, GroupNames groups) private AuthorizationInfo createAuthorizationInfo(User user,
GroupNames groups)
{ {
Builder<Permission> builder = ImmutableList.builder(); Set<String> roles;
if (user.isActive()) if (user.isAdmin())
{ {
if (user.isAdmin()) if (logger.isDebugEnabled())
{ {
//J- logger.debug("grant admin role for user {}", user.getName());
builder.add(
new RepositoryPermission(
RepositoryPermission.WILDCARD,
PermissionType.OWNER
)
);
//J+
}
else
{
collectRepositoryPermissions(builder, user, groups);
collectGlobalPermissions(builder, user, groups);
} }
roles = ImmutableSet.of(Role.USER, Role.ADMIN);
}
else
{
roles = ImmutableSet.of(Role.USER);
} }
return builder.build(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
Builder<Permission> permissions = ImmutableList.builder();
collectGlobalPermissions(permissions, user, groups);
collectRepositoryPermissions(permissions, user, groups);
info.addObjectPermissions(permissions.build());
return info;
} }
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
@@ -375,7 +422,7 @@ public class PermissionCollector
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */
private Cache<String, List> cache; private Cache<String, AuthorizationInfo> cache;
/** Field description */ /** Field description */
private RepositoryDAO repositoryDAO; private RepositoryDAO repositoryDAO;

View File

@@ -37,7 +37,6 @@ package sonia.scm.security;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@@ -52,7 +51,6 @@ 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.authz.SimpleAuthorizationInfo;
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.apache.shiro.subject.SimplePrincipalCollection;
@@ -61,8 +59,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEvent; import sonia.scm.HandlerEvent;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.event.Subscriber; import sonia.scm.event.Subscriber;
import sonia.scm.group.Group; import sonia.scm.group.Group;
@@ -71,7 +67,6 @@ import sonia.scm.group.GroupNames;
import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManager;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserDAO; import sonia.scm.user.UserDAO;
import sonia.scm.user.UserEvent;
import sonia.scm.user.UserEventHack; import sonia.scm.user.UserEventHack;
import sonia.scm.user.UserException; import sonia.scm.user.UserException;
import sonia.scm.user.UserManager; import sonia.scm.user.UserManager;
@@ -102,9 +97,6 @@ public class ScmRealm extends AuthorizingRealm
/** Field description */ /** Field description */
public static final String NAME = "scm"; public static final String NAME = "scm";
/** Field description */
private static final String CACHE_NAME = "sonia.cache.authorizing";
/** Field description */ /** Field description */
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS"; private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
@@ -135,8 +127,8 @@ public class ScmRealm extends AuthorizingRealm
*/ */
@Inject @Inject
public ScmRealm(ScmConfiguration configuration, public ScmRealm(ScmConfiguration configuration,
PermissionCollector collector, CacheManager cacheManager, AuthorizationCollector collector,UserManager userManager,
UserManager userManager, GroupManager groupManager, UserDAO userDAO, GroupManager groupManager, UserDAO userDAO,
AuthenticationManager authenticator, RepositoryManager manager, AuthenticationManager authenticator, RepositoryManager manager,
Provider<HttpServletRequest> requestProvider, Provider<HttpServletRequest> requestProvider,
Provider<HttpServletResponse> responseProvider) Provider<HttpServletResponse> responseProvider)
@@ -150,10 +142,6 @@ public class ScmRealm extends AuthorizingRealm
this.requestProvider = requestProvider; this.requestProvider = requestProvider;
this.responseProvider = responseProvider; this.responseProvider = responseProvider;
// init cache
this.cache = cacheManager.getCache(String.class, AuthorizationInfo.class,
CACHE_NAME);
// set token class // set token class
setAuthenticationTokenClass(UsernamePasswordToken.class); setAuthenticationTokenClass(UsernamePasswordToken.class);
@@ -168,30 +156,6 @@ public class ScmRealm extends AuthorizingRealm
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param event
*/
@Subscribe
public void onEvent(UserEvent event)
{
if (event.getEventType().isPost())
{
User user = event.getItem();
if (logger.isDebugEnabled())
{
logger.debug(
"clear cache of user {}, because user properties have changed",
user.getName());
}
cache.remove(user.getId());
}
}
/** /**
* Method description * Method description
* *
@@ -250,29 +214,7 @@ public class ScmRealm extends AuthorizingRealm
protected AuthorizationInfo doGetAuthorizationInfo( protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) PrincipalCollection principals)
{ {
User user = principals.oneByType(User.class); return collector.collect(principals);
AuthorizationInfo info = cache.get(user.getId());
if (info == null)
{
if (logger.isTraceEnabled())
{
logger.trace("collect AuthorizationInfo for user {}", user.getName());
}
GroupNames groups = principals.oneByType(GroupNames.class);
info = createAuthorizationInfo(user, groups);
cache.put(user.getId(), info);
}
else if (logger.isTraceEnabled())
{
logger.trace("retrieve AuthorizationInfo for user {} from cache",
user.getName());
}
return info;
} }
/** /**
@@ -476,39 +418,6 @@ public class ScmRealm extends AuthorizingRealm
return new SimpleAuthenticationInfo(collection, token.getPassword()); return new SimpleAuthenticationInfo(collection, token.getPassword());
} }
/**
* Method description
*
*
* @param user
* @param groups
*
* @return
*/
private AuthorizationInfo createAuthorizationInfo(User user,
GroupNames groups)
{
Set<String> roles = Sets.newHashSet();
roles.add(Role.USER);
if (user.isAdmin())
{
if (logger.isDebugEnabled())
{
logger.debug("grant admin role for user {}", user.getName());
}
roles.add(Role.ADMIN);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
info.addObjectPermissions(collector.collect(user, groups));
return info;
}
/** /**
* Method description * Method description
* *
@@ -628,10 +537,7 @@ public class ScmRealm extends AuthorizingRealm
private AuthenticationManager authenticator; private AuthenticationManager authenticator;
/** Field description */ /** Field description */
private Cache<String, AuthorizationInfo> cache; private AuthorizationCollector collector;
/** Field description */
private PermissionCollector collector;
/** Field description */ /** Field description */
private ScmConfiguration configuration; private ScmConfiguration configuration;

View File

@@ -126,6 +126,7 @@
timeToIdleSeconds="1200" timeToIdleSeconds="1200"
timeToLiveSeconds="2400" timeToLiveSeconds="2400"
diskPersistent="false" diskPersistent="false"
copyOnRead="true"
/> />
<!-- <!--

View File

@@ -60,6 +60,7 @@ http://bitbucket.org/sdorra/scm-manager
maximumSize="1000" maximumSize="1000"
expireAfterAccess="1200" expireAfterAccess="1200"
expireAfterWrite="2400" expireAfterWrite="2400"
copyStrategy="read"
/> />
<!-- <!--

View File

@@ -478,7 +478,7 @@ public class ScmRealmTest
CacheManager cacheManager = new MapCacheManager(); CacheManager cacheManager = new MapCacheManager();
PermissionCollector collector = new PermissionCollector( AuthorizationCollector collector = new AuthorizationCollector(
cacheManager, cacheManager,
repositoryDAO, repositoryDAO,
securitySystem, securitySystem,
@@ -488,7 +488,7 @@ public class ScmRealmTest
return new ScmRealm( return new ScmRealm(
new ScmConfiguration(), new ScmConfiguration(),
collector, collector,
cacheManager, // cacheManager,
userManager, userManager,
groupManager, groupManager,
userDAO, userDAO,