define AuthorizationCollector as extension point with multiple implmentations

This commit is contained in:
Sebastian Sdorra
2019-02-18 18:01:11 +01:00
parent 1a6e0dff8f
commit aec66c023a
7 changed files with 91 additions and 52 deletions

View File

@@ -77,15 +77,13 @@ public final class ScmState
* @param repositoryTypes available repository types * @param repositoryTypes available repository types
* @param defaultUserType default user type * @param defaultUserType default user type
* @param clientConfig client configuration * @param clientConfig client configuration
* @param assignedPermission assigned permissions
* @param availablePermissions list of available permissions * @param availablePermissions list of available permissions
* *
* @since 2.0.0 * @since 2.0.0
*/ */
public ScmState(String version, User user, Collection<String> groups, public ScmState(String version, User user, Collection<String> groups,
String token, Collection<RepositoryType> repositoryTypes, String defaultUserType, String token, Collection<RepositoryType> repositoryTypes, String defaultUserType,
ScmClientConfig clientConfig, List<String> assignedPermission, ScmClientConfig clientConfig, Collection<PermissionDescriptor> availablePermissions)
Collection<PermissionDescriptor> availablePermissions)
{ {
this.version = version; this.version = version;
this.user = user; this.user = user;
@@ -94,24 +92,11 @@ public final class ScmState
this.repositoryTypes = repositoryTypes; this.repositoryTypes = repositoryTypes;
this.clientConfig = clientConfig; this.clientConfig = clientConfig;
this.defaultUserType = defaultUserType; this.defaultUserType = defaultUserType;
this.assignedPermissions = assignedPermission;
this.availablePermissions = availablePermissions; this.availablePermissions = availablePermissions;
} }
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/**
* Return a list of assigned permissions.
*
*
* @return list of assigned permissions
* @since 1.31
*/
public List<String> getAssignedPermissions()
{
return assignedPermissions;
}
/** /**
* Returns a list of available global permissions. * Returns a list of available global permissions.
* *
@@ -225,9 +210,6 @@ public final class ScmState
/** authentication token */ /** authentication token */
private String token; private String token;
/** Field description */
private List<String> assignedPermissions;
/** /**
* Avaliable global permission * Avaliable global permission
* @since 1.31 * @since 1.31

View File

@@ -74,20 +74,17 @@ public final class ScmStateFactory
* @param repositoryManger repository manager * @param repositoryManger repository manager
* @param userManager user manager * @param userManager user manager
* @param securitySystem security system * @param securitySystem security system
* @param authorizationCollector authorization collector
*/ */
@Inject @Inject
public ScmStateFactory(SCMContextProvider contextProvider, public ScmStateFactory(SCMContextProvider contextProvider,
ScmConfiguration configuration, RepositoryManager repositoryManger, ScmConfiguration configuration, RepositoryManager repositoryManger,
UserManager userManager, SecuritySystem securitySystem, UserManager userManager, SecuritySystem securitySystem)
AuthorizationCollector authorizationCollector)
{ {
this.contextProvider = contextProvider; this.contextProvider = contextProvider;
this.configuration = configuration; this.configuration = configuration;
this.repositoryManger = repositoryManger; this.repositoryManger = repositoryManger;
this.userManager = userManager; this.userManager = userManager;
this.securitySystem = securitySystem; this.securitySystem = securitySystem;
this.authorizationCollector = authorizationCollector;
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@@ -101,8 +98,7 @@ public final class ScmStateFactory
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ScmState createAnonymousState() public ScmState createAnonymousState()
{ {
return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, null, return createState(SCMContext.ANONYMOUS, Collections.EMPTY_LIST, null, Collections.EMPTY_LIST);
Collections.EMPTY_LIST, Collections.EMPTY_LIST);
} }
/** /**
@@ -141,15 +137,11 @@ public final class ScmStateFactory
ap = securitySystem.getAvailablePermissions(); ap = securitySystem.getAvailablePermissions();
} }
List<String> permissions = return createState(user, groups.getCollection(), token, ap);
ImmutableList.copyOf(
authorizationCollector.collect().getStringPermissions());
return createState(user, groups.getCollection(), token, permissions, ap);
} }
private ScmState createState(User user, Collection<String> groups, private ScmState createState(User user, Collection<String> groups,
String token, List<String> assignedPermissions, String token,
Collection<PermissionDescriptor> availablePermissions) Collection<PermissionDescriptor> availablePermissions)
{ {
User u = user.clone(); User u = user.clone();
@@ -159,15 +151,11 @@ public final class ScmStateFactory
return new ScmState(contextProvider.getVersion(), u, groups, token, return new ScmState(contextProvider.getVersion(), u, groups, token,
repositoryManger.getConfiguredTypes(), userManager.getDefaultType(), repositoryManger.getConfiguredTypes(), userManager.getDefaultType(),
new ScmClientConfig(configuration), assignedPermissions, new ScmClientConfig(configuration), availablePermissions);
availablePermissions);
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** authorization collector */
private final AuthorizationCollector authorizationCollector;
/** configuration */ /** configuration */
private final ScmConfiguration configuration; private final ScmConfiguration configuration;

View File

@@ -34,6 +34,7 @@ package sonia.scm.security;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import sonia.scm.plugin.ExtensionPoint; import sonia.scm.plugin.ExtensionPoint;
/** /**
@@ -42,15 +43,16 @@ import sonia.scm.plugin.ExtensionPoint;
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 2.0.0 * @since 2.0.0
*/ */
@ExtensionPoint(multi = false) @ExtensionPoint
public interface AuthorizationCollector public interface AuthorizationCollector
{ {
/** /**
* Returns {@link AuthorizationInfo} for the authenticated user. * Returns {@link AuthorizationInfo} for the authenticated user.
* *
* @param principalCollection collected principals
* *
* @return {@link AuthorizationInfo} for authenticated user * @return {@link AuthorizationInfo} for authenticated user
*/ */
public AuthorizationInfo collect(); AuthorizationInfo collect(PrincipalCollection principalCollection);
} }

View File

@@ -36,6 +36,7 @@ package sonia.scm.security;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.github.legman.Subscribe; import com.github.legman.Subscribe;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@@ -118,8 +119,8 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
* *
* @return * @return
*/ */
@Override @VisibleForTesting
public AuthorizationInfo collect() AuthorizationInfo collect()
{ {
AuthorizationInfo authorizationInfo; AuthorizationInfo authorizationInfo;
Subject subject = SecurityUtils.getSubject(); Subject subject = SecurityUtils.getSubject();
@@ -143,6 +144,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
* *
* @return * @return
*/ */
@Override
public AuthorizationInfo collect(PrincipalCollection principals) public AuthorizationInfo collect(PrincipalCollection principals)
{ {
Preconditions.checkNotNull(principals, "principals parameter is required"); Preconditions.checkNotNull(principals, "principals parameter is required");

View File

@@ -42,9 +42,11 @@ import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.PasswordMatcher; import org.apache.shiro.authc.credential.PasswordMatcher;
import org.apache.shiro.authc.credential.PasswordService; import org.apache.shiro.authc.credential.PasswordService;
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 sonia.scm.group.GroupNames; import sonia.scm.group.GroupNames;
import sonia.scm.plugin.Extension; import sonia.scm.plugin.Extension;
@@ -56,6 +58,8 @@ import javax.inject.Singleton;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Set;
/** /**
* Default authorizing realm. * Default authorizing realm.
* *
@@ -85,14 +89,13 @@ public class DefaultRealm extends AuthorizingRealm
* *
* *
* @param service * @param service
* @param collector * @param authorizationCollectors
* @param helperFactory * @param helperFactory
*/ */
@Inject @Inject
public DefaultRealm(PasswordService service, public DefaultRealm(PasswordService service, Set<AuthorizationCollector> authorizationCollectors, DAORealmHelperFactory helperFactory)
DefaultAuthorizationCollector collector, DAORealmHelperFactory helperFactory)
{ {
this.collector = collector; this.authorizationCollectors = authorizationCollectors;
this.helper = helperFactory.create(REALM); this.helper = helperFactory.create(REALM);
PasswordMatcher matcher = new PasswordMatcher(); PasswordMatcher matcher = new PasswordMatcher();
@@ -133,8 +136,7 @@ public class DefaultRealm extends AuthorizingRealm
@Override @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{ {
AuthorizationInfo info = collector.collect(principals); AuthorizationInfo info = collectors(principals);
Scope scope = principals.oneByType(Scope.class); Scope scope = principals.oneByType(Scope.class);
if (scope != null && ! scope.isEmpty()) { if (scope != null && ! scope.isEmpty()) {
LOG.trace("filter permissions by scope {}", scope); LOG.trace("filter permissions by scope {}", scope);
@@ -144,13 +146,36 @@ public class DefaultRealm extends AuthorizingRealm
} }
return filtered; return filtered;
} else if (LOG.isTraceEnabled()) { } else if (LOG.isTraceEnabled()) {
LOG.trace("principal does not contain scope informations, returning all permissions"); LOG.trace("principal does not contain scope information, returning all permissions");
log(principals, info, null); log(principals, info, null);
} }
return info; return info;
} }
private AuthorizationInfo collectors(PrincipalCollection principals) {
SimpleAuthorizationInfo merged = new SimpleAuthorizationInfo();
for (AuthorizationCollector collector : authorizationCollectors) {
AuthorizationInfo authorizationInfo = collector.collect(principals);
merge(merged, authorizationInfo);
}
return merged;
}
private void merge(SimpleAuthorizationInfo merged, AuthorizationInfo authorizationInfo) {
if (authorizationInfo != null) {
if (authorizationInfo.getRoles() != null) {
merged.addRoles(authorizationInfo.getRoles());
}
if (authorizationInfo.getObjectPermissions() != null) {
merged.addObjectPermissions(authorizationInfo.getObjectPermissions());
}
if (authorizationInfo.getStringPermissions() != null) {
merged.addStringPermissions(authorizationInfo.getStringPermissions());
}
}
}
private void log( PrincipalCollection collection, AuthorizationInfo original, AuthorizationInfo filtered ) { private void log( PrincipalCollection collection, AuthorizationInfo original, AuthorizationInfo filtered ) {
StringBuilder buffer = new StringBuilder("authorization summary: "); StringBuilder buffer = new StringBuilder("authorization summary: ");
@@ -190,8 +215,8 @@ public class DefaultRealm extends AuthorizingRealm
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** default authorization collector */ /** set of authorization collector */
private final DefaultAuthorizationCollector collector; private final Set<AuthorizationCollector> authorizationCollectors;
/** realm helper */ /** realm helper */
private final DAORealmHelper helper; private final DAORealmHelper helper;

View File

@@ -205,7 +205,7 @@ private long calculateAverage(List<Long> times) {
@Override @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return authzCollector.collect(); return authzCollector.collect(principals);
} }
} }

View File

@@ -71,7 +71,11 @@ import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.apache.shiro.authz.AuthorizationInfo; 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.SimpleAuthorizationInfo;
@@ -132,6 +136,36 @@ public class DefaultRealmTest
assertThat(realmsAutz.getStringPermissions(), Matchers.contains("repository:*")); assertThat(realmsAutz.getStringPermissions(), Matchers.contains("repository:*"));
} }
@Test
public void testGetAuthorizationInfoWithMultipleAuthorizationCollectors(){
SimplePrincipalCollection col = new SimplePrincipalCollection();
col.add(Scope.empty(), DefaultRealm.REALM);
SimpleAuthorizationInfo collectedFromDefault = new SimpleAuthorizationInfo();
collectedFromDefault.addStringPermission("repository:*");
when(collector.collect(col)).thenReturn(collectedFromDefault);
SimpleAuthorizationInfo collectedFromSecond = new SimpleAuthorizationInfo();
collectedFromSecond.addStringPermission("user:*");
collectedFromSecond.addRole("awesome");
AuthorizationCollector secondCollector = principalCollection -> collectedFromSecond;
authorizationCollectors.add(secondCollector);
SimpleAuthorizationInfo collectedFromThird = new SimpleAuthorizationInfo();
Permission permission = p -> false;
collectedFromThird.addObjectPermission(permission);
collectedFromThird.addRole("awesome");
AuthorizationCollector thirdCollector = principalCollection -> collectedFromThird;
authorizationCollectors.add(thirdCollector);
AuthorizationInfo realmsAuthz = realm.doGetAuthorizationInfo(col);
assertThat(realmsAuthz.getObjectPermissions(), contains(permission));
assertThat(realmsAuthz.getStringPermissions(), containsInAnyOrder("repository:*", "user:*"));
assertThat(realmsAuthz.getRoles(), Matchers.contains("awesome"));
}
/** /**
* Tests {@link DefaultRealm#doGetAuthorizationInfo(PrincipalCollection)} with empty scope. * Tests {@link DefaultRealm#doGetAuthorizationInfo(PrincipalCollection)} with empty scope.
*/ */
@@ -284,7 +318,11 @@ public class DefaultRealmTest
// use a small number of iterations for faster test execution // use a small number of iterations for faster test execution
hashService.setHashIterations(512); hashService.setHashIterations(512);
service.setHashService(hashService); service.setHashService(hashService);
realm = new DefaultRealm(service, collector, helperFactory);
authorizationCollectors = new HashSet<>();
authorizationCollectors.add(collector);
realm = new DefaultRealm(service, authorizationCollectors, helperFactory);
// set permission resolver // set permission resolver
realm.setPermissionResolver(new WildcardPermissionResolver()); realm.setPermissionResolver(new WildcardPermissionResolver());
@@ -358,6 +396,8 @@ public class DefaultRealmTest
@Mock @Mock
private DefaultAuthorizationCollector collector; private DefaultAuthorizationCollector collector;
private Set<AuthorizationCollector> authorizationCollectors;
@Mock @Mock
private LoginAttemptHandler loginAttemptHandler; private LoginAttemptHandler loginAttemptHandler;