diff --git a/scm-core/src/main/java/sonia/scm/group/ExternalGroupNames.java b/scm-core/src/main/java/sonia/scm/group/ExternalGroupNames.java deleted file mode 100644 index 179e488236..0000000000 --- a/scm-core/src/main/java/sonia/scm/group/ExternalGroupNames.java +++ /dev/null @@ -1,22 +0,0 @@ -package sonia.scm.group; - -import java.util.Collection; - -/** - * This class represents all associated groups which are provided by external systems for a certain user. - * - * @author Sebastian Sdorra - * @since 2.0.0 - */ -public class ExternalGroupNames extends GroupNames { - public ExternalGroupNames() { - } - - public ExternalGroupNames(String groupName, String... groupNames) { - super(groupName, groupNames); - } - - public ExternalGroupNames(Collection collection) { - super(collection); - } -} diff --git a/scm-core/src/main/java/sonia/scm/group/GroupNames.java b/scm-core/src/main/java/sonia/scm/group/GroupNames.java deleted file mode 100644 index c28f9f5ef1..0000000000 --- a/scm-core/src/main/java/sonia/scm/group/GroupNames.java +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Copyright (c) 2010, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - - - -package sonia.scm.group; - -//~--- non-JDK imports -------------------------------------------------------- - -import com.google.common.base.Joiner; -import com.google.common.base.Objects; -import com.google.common.collect.Lists; - -import java.io.Serializable; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; - -//~--- JDK imports ------------------------------------------------------------ - -/** - * This class represents all associated groups for a user. - * - * @author Sebastian Sdorra - * @since 1.21 - */ -public class GroupNames implements Serializable, Iterable -{ - - /** - * Group for all authenticated users - * @since 1.31 - */ - public static final String AUTHENTICATED = "_authenticated"; - - /** Field description */ - private static final long serialVersionUID = 8615685985213897947L; - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - */ - public GroupNames() - { - this(Collections.emptyList()); - } - - /** - * Constructs ... - * - * - * @param groupName - * @param groupNames - */ - public GroupNames(String groupName, String... groupNames) - { - this(Lists.asList(groupName, groupNames)); - } - - /** - * Constructs ... - * - * - * @param collection - */ - public GroupNames(Collection collection) - { - this.collection = Collections.unmodifiableCollection(collection); - } - - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param groupName - * - * @return - */ - public boolean contains(String groupName) - { - return collection.contains(groupName); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(Object obj) - { - if (obj == null) - { - return false; - } - - if (getClass() != obj.getClass()) - { - return false; - } - - final GroupNames other = (GroupNames) obj; - - return Objects.equal(collection, other.collection); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() - { - return Objects.hashCode(collection); - } - - /** - * Method description - * - * - * @return - */ - @Override - public Iterator iterator() - { - return collection.iterator(); - } - - /** - * Method description - * - * - * @return - */ - @Override - public String toString() - { - return Joiner.on(", ").join(collection); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - public Collection getCollection() - { - return collection; - } - - - //~--- fields --------------------------------------------------------------- - /** Field description */ - private final Collection collection; -} diff --git a/scm-core/src/main/java/sonia/scm/security/AccessTokenBuilder.java b/scm-core/src/main/java/sonia/scm/security/AccessTokenBuilder.java index 0924716bd8..afe81ac27f 100644 --- a/scm-core/src/main/java/sonia/scm/security/AccessTokenBuilder.java +++ b/scm-core/src/main/java/sonia/scm/security/AccessTokenBuilder.java @@ -99,15 +99,6 @@ public interface AccessTokenBuilder { */ AccessTokenBuilder scope(Scope scope); - /** - * Define the logged in user as member of the given groups. - * - * @param groups group names - * - * @return {@code this} - */ - AccessTokenBuilder groups(String... groups); - /** * Creates a new {@link AccessToken} with the provided settings. * diff --git a/scm-core/src/main/java/sonia/scm/security/DAORealmHelperFactory.java b/scm-core/src/main/java/sonia/scm/security/DAORealmHelperFactory.java index b503ff8375..dd59de2ac8 100644 --- a/scm-core/src/main/java/sonia/scm/security/DAORealmHelperFactory.java +++ b/scm-core/src/main/java/sonia/scm/security/DAORealmHelperFactory.java @@ -31,7 +31,6 @@ package sonia.scm.security; import sonia.scm.cache.CacheManager; -import sonia.scm.group.GroupDAO; import sonia.scm.user.UserDAO; import javax.inject.Inject; @@ -47,21 +46,17 @@ public final class DAORealmHelperFactory { private final LoginAttemptHandler loginAttemptHandler; private final UserDAO userDAO; private final CacheManager cacheManager; - private final GroupResolver groupResolver; /** * Constructs a new instance. * @param loginAttemptHandler login attempt handler * @param userDAO user dao - * @param groupDAO group dao * @param cacheManager - * @param groupResolver */ @Inject - public DAORealmHelperFactory(LoginAttemptHandler loginAttemptHandler, UserDAO userDAO, GroupDAO groupDAO, CacheManager cacheManager, GroupResolver groupResolver) { + public DAORealmHelperFactory(LoginAttemptHandler loginAttemptHandler, UserDAO userDAO, CacheManager cacheManager) { this.loginAttemptHandler = loginAttemptHandler; this.userDAO = userDAO; - this.groupResolver = groupResolver; this.cacheManager = cacheManager; } diff --git a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java index b209184902..b2175f304a 100644 --- a/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java +++ b/scm-core/src/main/java/sonia/scm/security/SyncingRealmHelper.java @@ -32,25 +32,15 @@ import com.google.inject.Inject; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.subject.SimplePrincipalCollection; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.AlreadyExistsException; import sonia.scm.NotFoundException; -import sonia.scm.cache.CacheManager; -import sonia.scm.group.ExternalGroupNames; import sonia.scm.group.Group; -import sonia.scm.group.GroupDAO; import sonia.scm.group.GroupManager; import sonia.scm.plugin.Extension; import sonia.scm.user.User; import sonia.scm.user.UserManager; import sonia.scm.web.security.AdministrationContext; -import java.util.Collection; -import java.util.Collections; - -import static java.util.Arrays.asList; - /** * Helper class for syncing realms. The class should simplify the creation of realms, which are syncing authenticated * users with the local database. @@ -61,12 +51,9 @@ import static java.util.Arrays.asList; @Extension public final class SyncingRealmHelper { - private static final Logger LOG = LoggerFactory.getLogger(SyncingRealmHelper.class); - private final AdministrationContext ctx; private final UserManager userManager; private final GroupManager groupManager; - private final CacheManager cacheManager; /** * Constructs a new SyncingRealmHelper. @@ -74,133 +61,28 @@ public final class SyncingRealmHelper { * @param ctx administration context * @param userManager user manager * @param groupManager group manager - * @param groupDAO group dao */ @Inject - public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager, GroupDAO groupDAO, CacheManager cacheManager) { + public SyncingRealmHelper(AdministrationContext ctx, UserManager userManager, GroupManager groupManager) { this.ctx = ctx; this.userManager = userManager; this.groupManager = groupManager; - this.cacheManager = cacheManager; } - /** - * Create {@link AuthenticationInfo} from user and groups. - */ - public AuthenticationInfoBuilder.ForRealm authenticationInfo() { - return new AuthenticationInfoBuilder().new ForRealm(); - } - - public class AuthenticationInfoBuilder { - private String realm; - private User user; - private Collection groups = Collections.emptySet(); - private Collection externalGroups = Collections.emptySet(); - - private AuthenticationInfo build() { - return SyncingRealmHelper.this.createAuthenticationInfo(realm, user, groups, externalGroups); - } - - public class ForRealm { - private ForRealm() { - } - - /** - * Sets the realm. - * @param realm name of the realm - */ - public ForUser forRealm(String realm) { - AuthenticationInfoBuilder.this.realm = realm; - return AuthenticationInfoBuilder.this.new ForUser(); - } - } - - public class ForUser { - private ForUser() { - } - - /** - * Sets the user. - * @param user authenticated user - */ - public AuthenticationInfoBuilder.WithGroups andUser(User user) { - AuthenticationInfoBuilder.this.user = user; - return AuthenticationInfoBuilder.this.new WithGroups(); - } - } - - public class WithGroups { - private WithGroups() { - } - - /** - * Set the internal groups for the user. - * @param groups groups of the authenticated user - * @return builder step for groups - */ - public WithGroups withGroups(String... groups) { - return withGroups(asList(groups)); - } - - /** - * Set the internal groups for the user. - * @param groups groups of the authenticated user - * @return builder step for groups - */ - public WithGroups withGroups(Collection groups) { - AuthenticationInfoBuilder.this.groups = groups; - return this; - } - - /** - * Set the external groups for the user. - * @param externalGroups external groups of the authenticated user - * @return builder step for groups - */ - public WithGroups withExternalGroups(String... externalGroups) { - return withExternalGroups(asList(externalGroups)); - } - - /** - * Set the external groups for the user. - * @param externalGroups external groups of the authenticated user - * @return builder step for groups - */ - public WithGroups withExternalGroups(Collection externalGroups) { - AuthenticationInfoBuilder.this.externalGroups = externalGroups; - return this; - } - - /** - * Builds the {@link AuthenticationInfo} from the given options. - * - * @return complete autentication info - */ - public AuthenticationInfo build() { - return AuthenticationInfoBuilder.this.build(); - } - } - } - - //~--- methods -------------------------------------------------------------- - /** * Create {@link AuthenticationInfo} from user and groups. * * * @param realm name of the realm * @param user authenticated user - * @param groups groups of the authenticated user * * @return authentication info */ - private AuthenticationInfo createAuthenticationInfo(String realm, User user, - Collection groups, Collection externalGroups) { + public AuthenticationInfo createAuthenticationInfo(String realm, User user) { SimplePrincipalCollection collection = new SimplePrincipalCollection(); collection.add(user.getId(), realm); collection.add(user, realm); - collection.add(new ExternalGroupNames(externalGroups), realm); return new SimpleAuthenticationInfo(collection, user.getPassword()); } diff --git a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java index b7fbd97aac..ca7c2efdc6 100644 --- a/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java +++ b/scm-core/src/test/java/sonia/scm/security/SyncingRealmHelperTest.java @@ -37,17 +37,13 @@ package sonia.scm.security; import com.google.common.base.Throwables; import org.apache.shiro.authc.AuthenticationInfo; -import org.assertj.core.api.Assertions; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.AlreadyExistsException; -import sonia.scm.cache.CacheManager; -import sonia.scm.group.ExternalGroupNames; import sonia.scm.group.Group; -import sonia.scm.group.GroupDAO; import sonia.scm.group.GroupManager; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -81,12 +77,6 @@ public class SyncingRealmHelperTest { @Mock private UserManager userManager; - @Mock - private GroupDAO groupDAO; - - @Mock - CacheManager cacheManager; - private SyncingRealmHelper helper; /** @@ -112,7 +102,7 @@ public class SyncingRealmHelperTest { } }; - helper = new SyncingRealmHelper(ctx, userManager, groupManager, groupDAO, cacheManager); + helper = new SyncingRealmHelper(ctx, userManager, groupManager); } /** @@ -189,27 +179,11 @@ public class SyncingRealmHelperTest { verify(userManager, times(1)).modify(user); } - @Test - public void builderShouldSetExternalGroups() { - AuthenticationInfo authenticationInfo = helper - .authenticationInfo() - .forRealm("unit-test") - .andUser(new User("ziltoid")) - .withExternalGroups("external") - .build(); - - ExternalGroupNames groupNames = authenticationInfo.getPrincipals().oneByType(ExternalGroupNames.class); - Assertions.assertThat(groupNames.getCollection()).containsOnly("external"); - } @Test public void builderShouldSetValues() { User user = new User("ziltoid"); - AuthenticationInfo authInfo = helper - .authenticationInfo() - .forRealm("unit-test") - .andUser(user) - .build(); + AuthenticationInfo authInfo = helper.createAuthenticationInfo("unit-test", user); assertNotNull(authInfo); assertEquals("ziltoid", authInfo.getPrincipals().getPrimaryPrincipal()); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDto.java index 84fbbfe290..a87ad7df17 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDto.java @@ -7,7 +7,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.util.List; +import java.util.Set; @Getter @Setter @@ -17,7 +17,7 @@ public class MeDto extends HalRepresentation { private String name; private String displayName; private String mail; - private List groups; + private Set groups; MeDto(Links links, Embedded embedded) { super(links, embedded); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java index b5e1998066..c2bebd389a 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/MeDtoFactory.java @@ -1,18 +1,16 @@ package sonia.scm.api.v2.resources; -import com.google.common.collect.ImmutableList; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Links; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; -import sonia.scm.group.GroupNames; +import sonia.scm.group.GroupCollector; import sonia.scm.user.User; import sonia.scm.user.UserManager; import sonia.scm.user.UserPermissions; import javax.inject.Inject; -import java.util.Collections; import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; @@ -22,11 +20,13 @@ public class MeDtoFactory extends HalAppenderMapper { private final ResourceLinks resourceLinks; private final UserManager userManager; + private final GroupCollector groupCollector; @Inject - public MeDtoFactory(ResourceLinks resourceLinks, UserManager userManager) { + public MeDtoFactory(ResourceLinks resourceLinks, UserManager userManager, GroupCollector groupCollector) { this.resourceLinks = resourceLinks; this.userManager = userManager; + this.groupCollector = groupCollector; } public MeDto create() { @@ -35,16 +35,12 @@ public class MeDtoFactory extends HalAppenderMapper { MeDto dto = createDto(user); mapUserProperties(user, dto); - mapGroups(principals, dto); + mapGroups(user, dto); return dto; } - private void mapGroups(PrincipalCollection principals, MeDto dto) { - Iterable groups = principals.oneByType(GroupNames.class); - if (groups == null) { - groups = Collections.emptySet(); - } - dto.setGroups(ImmutableList.copyOf(groups)); + private void mapGroups(User user, MeDto dto) { + dto.setGroups(groupCollector.collect(user.getName())); } private void mapUserProperties(User user, MeDto dto) { diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java index baff3b951c..28f61df34f 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultAuthorizationCollector.java @@ -52,17 +52,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; -import sonia.scm.group.GroupNames; +import sonia.scm.group.GroupCollector; import sonia.scm.group.GroupPermissions; import sonia.scm.plugin.Extension; -import sonia.scm.repository.RepositoryPermission; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; +import sonia.scm.repository.RepositoryPermission; import sonia.scm.user.User; import sonia.scm.user.UserPermissions; import sonia.scm.util.Util; import java.util.Collection; +import java.util.Set; //~--- JDK imports ------------------------------------------------------------ @@ -88,19 +89,21 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector /** * Constructs ... - * @param cacheManager + * @param cacheManager * @param repositoryDAO * @param securitySystem * @param repositoryPermissionProvider + * @param groupCollector */ @Inject public DefaultAuthorizationCollector(CacheManager cacheManager, - RepositoryDAO repositoryDAO, SecuritySystem securitySystem, RepositoryPermissionProvider repositoryPermissionProvider) + RepositoryDAO repositoryDAO, SecuritySystem securitySystem, RepositoryPermissionProvider repositoryPermissionProvider, GroupCollector groupCollector) { this.cache = cacheManager.getCache(CACHE_NAME); this.repositoryDAO = repositoryDAO; this.securitySystem = securitySystem; this.repositoryPermissionProvider = repositoryPermissionProvider; + this.groupCollector = groupCollector; } //~--- methods -------------------------------------------------------------- @@ -145,16 +148,16 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector Preconditions.checkNotNull(user, "no user found in principal collection"); - GroupNames groupNames = principals.oneByType(GroupNames.class); + Set groups = groupCollector.collect(user.getName()); - CacheKey cacheKey = new CacheKey(user.getId(), groupNames); + CacheKey cacheKey = new CacheKey(user.getId(), groups); AuthorizationInfo info = cache.get(cacheKey); if (info == null) { logger.trace("collect AuthorizationInfo for user {}", user.getName()); - info = createAuthorizationInfo(user, groupNames); + info = createAuthorizationInfo(user, groups); cache.put(cacheKey, info); } else if (logger.isTraceEnabled()) @@ -166,7 +169,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector } private void collectGlobalPermissions(Builder builder, - final User user, final GroupNames groups) + final User user, final Set groups) { Collection globalPermissions = securitySystem.getPermissions((AssignedPermission input) -> isUserPermitted(user, groups, input)); @@ -181,7 +184,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector } private void collectRepositoryPermissions(Builder builder, User user, - GroupNames groups) + Set groups) { for (Repository repository : repositoryDAO.getAll()) { @@ -190,7 +193,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector } private void collectRepositoryPermissions(Builder builder, - Repository repository, User user, GroupNames groups) + Repository repository, User user, Set groups) { Collection repositoryPermissions = repository.getPermissions(); @@ -245,7 +248,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector .getVerbs(); } - private AuthorizationInfo createAuthorizationInfo(User user, GroupNames groups) { + private AuthorizationInfo createAuthorizationInfo(User user, Set groups) { Builder builder = ImmutableSet.builder(); collectGlobalPermissions(builder, user, groups); @@ -279,7 +282,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector //~--- get methods ---------------------------------------------------------- - private boolean isUserPermitted(User user, GroupNames groups, + private boolean isUserPermitted(User user, Set groups, PermissionObject perm) { //J- @@ -314,7 +317,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector */ private static class CacheKey { - private CacheKey(String username, GroupNames groupnames) + private CacheKey(String username, Set groupnames) { this.username = username; this.groupnames = groupnames; @@ -356,7 +359,7 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector //~--- fields ------------------------------------------------------------- /** group names */ - private final GroupNames groupnames; + private final Set groupnames; /** username */ private final String username; @@ -374,4 +377,5 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector private final SecuritySystem securitySystem; private final RepositoryPermissionProvider repositoryPermissionProvider; + private final GroupCollector groupCollector; } diff --git a/scm-webapp/src/main/java/sonia/scm/security/DefaultRealm.java b/scm-webapp/src/main/java/sonia/scm/security/DefaultRealm.java index bacbd9b314..245dcadb78 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/DefaultRealm.java +++ b/scm-webapp/src/main/java/sonia/scm/security/DefaultRealm.java @@ -34,7 +34,6 @@ package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.annotations.VisibleForTesting; - import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; @@ -45,21 +44,16 @@ import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; - -import org.apache.shiro.subject.SimplePrincipalCollection; -import sonia.scm.group.GroupNames; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sonia.scm.plugin.Extension; -//~--- JDK imports ------------------------------------------------------------ - import javax.inject.Inject; import javax.inject.Singleton; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.Set; +//~--- JDK imports ------------------------------------------------------------ + /** * Default authorizing realm. * @@ -149,7 +143,7 @@ public class DefaultRealm extends AuthorizingRealm LOG.trace("principal does not contain scope information, returning all permissions"); log(principals, info, null); } - + return info; } @@ -180,8 +174,6 @@ public class DefaultRealm extends AuthorizingRealm StringBuilder buffer = new StringBuilder("authorization summary: "); buffer.append(SEPARATOR).append("username : ").append(collection.getPrimaryPrincipal()); - buffer.append(SEPARATOR).append("groups : "); - append(buffer, collection.oneByType(GroupNames.class)); buffer.append(SEPARATOR).append("roles : "); append(buffer, original.getRoles()); buffer.append(SEPARATOR).append("scope : "); diff --git a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java index cc1d4be7a7..5a74f77502 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java +++ b/scm-webapp/src/main/java/sonia/scm/security/JwtAccessTokenBuilder.java @@ -40,11 +40,9 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import sonia.scm.group.ExternalGroupNames; import java.time.Clock; import java.time.Instant; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -139,12 +137,6 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder { return this; } - @Override - public JwtAccessTokenBuilder groups(String... groups) { - Collections.addAll(this.groups, groups); - return this; - } - JwtAccessTokenBuilder refreshExpiration(Instant refreshExpiration) { this.refreshExpiration = refreshExpiration; this.refreshableFor = 0; @@ -206,16 +198,6 @@ public final class JwtAccessTokenBuilder implements AccessTokenBuilder { claims.setIssuer(issuer); } - if (!groups.isEmpty()) { - claims.put(JwtAccessToken.GROUPS_CLAIM_KEY, groups); - } else { - Subject currentSubject = SecurityUtils.getSubject(); - ExternalGroupNames externalGroupNames = currentSubject.getPrincipals().oneByType(ExternalGroupNames.class); - if (externalGroupNames != null) { - claims.put(JwtAccessToken.GROUPS_CLAIM_KEY, externalGroupNames.getCollection().toArray(new String[]{})); - } - } - // sign token and create compact version String compact = Jwts.builder() .setClaims(claims) diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java index 0b380c8088..b62b6c63f3 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java @@ -38,7 +38,6 @@ package sonia.scm.web.security; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Singleton; - import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; @@ -46,21 +45,17 @@ import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.support.SubjectThreadState; import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadState; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import sonia.scm.SCMContext; -import sonia.scm.group.GroupNames; import sonia.scm.security.Role; import sonia.scm.user.User; import sonia.scm.util.AssertUtil; -//~--- JDK imports ------------------------------------------------------------ - +import javax.xml.bind.JAXB; import java.net.URL; -import javax.xml.bind.JAXB; +//~--- JDK imports ------------------------------------------------------------ /** * @@ -161,7 +156,6 @@ public class DefaultAdministrationContext implements AdministrationContext collection.add(adminUser.getId(), REALM); collection.add(adminUser, REALM); - collection.add(new GroupNames(), REALM); collection.add(AdministrationContextMarker.MARKER, REALM); return collection; diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java index 8a00c69229..d9572dc04c 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/MeDtoFactoryTest.java @@ -1,9 +1,9 @@ package sonia.scm.api.v2.resources; +import com.google.common.collect.ImmutableSet; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; -import org.assertj.core.util.Lists; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -12,7 +12,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; -import sonia.scm.group.GroupNames; +import sonia.scm.group.GroupCollector; import sonia.scm.user.User; import sonia.scm.user.UserManager; import sonia.scm.user.UserTestData; @@ -20,7 +20,6 @@ import sonia.scm.user.UserTestData; import java.net.URI; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -33,6 +32,9 @@ class MeDtoFactoryTest { @Mock private UserManager userManager; + @Mock + private GroupCollector groupCollector; + @Mock private Subject subject; @@ -42,7 +44,7 @@ class MeDtoFactoryTest { void setUpContext() { ThreadContext.bind(subject); ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri); - meDtoFactory = new MeDtoFactory(resourceLinks, userManager); + meDtoFactory = new MeDtoFactory(resourceLinks, userManager, groupCollector); } @AfterEach @@ -69,24 +71,16 @@ class MeDtoFactoryTest { @Test void shouldCreateMeDtoWithGroups() { - prepareSubject(UserTestData.createTrillian(), "HeartOfGold", "Puzzle42"); + when(groupCollector.collect("trillian")).thenReturn(ImmutableSet.of("HeartOfGold", "Puzzle42")); + prepareSubject(UserTestData.createTrillian()); MeDto dto = meDtoFactory.create(); assertThat(dto.getGroups()).containsOnly("HeartOfGold", "Puzzle42"); } - private void prepareSubject(User user, String... groups) { + private void prepareSubject(User user) { PrincipalCollection collection = mock(PrincipalCollection.class); when(subject.getPrincipals()).thenReturn(collection); - when(collection.oneByType(any(Class.class))).then(ic -> { - Class type = ic.getArgument(0); - if (type.isAssignableFrom(User.class)) { - return user; - } else if (type.isAssignableFrom(GroupNames.class)) { - return new GroupNames(Lists.newArrayList(groups)); - } else { - return null; - } - }); + when(collection.oneByType(User.class)).thenReturn(user); } @Test diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java index 8e7cb8a70e..930a06d249 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultAuthorizationCollectorTest.java @@ -33,6 +33,7 @@ package sonia.scm.security; import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.SubjectAware; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; @@ -49,7 +50,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; -import sonia.scm.group.GroupNames; +import sonia.scm.group.GroupCollector; import sonia.scm.repository.Repository; import sonia.scm.repository.RepositoryDAO; import sonia.scm.repository.RepositoryPermission; @@ -58,8 +59,6 @@ import sonia.scm.repository.RepositoryTestData; import sonia.scm.user.User; import sonia.scm.user.UserTestData; -import java.util.Collections; - import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -96,6 +95,9 @@ public class DefaultAuthorizationCollectorTest { @Mock private RepositoryPermissionProvider repositoryPermissionProvider; + @Mock + private GroupCollector groupCollector; + private DefaultAuthorizationCollector collector; @Rule @@ -107,7 +109,7 @@ public class DefaultAuthorizationCollectorTest { @Before public void setUp(){ when(cacheManager.getCache(Mockito.any(String.class))).thenReturn(cache); - collector = new DefaultAuthorizationCollector(cacheManager, repositoryDAO, securitySystem, repositoryPermissionProvider); + collector = new DefaultAuthorizationCollector(cacheManager, repositoryDAO, securitySystem, repositoryPermissionProvider, groupCollector); } /** @@ -290,9 +292,13 @@ public class DefaultAuthorizationCollectorTest { SimplePrincipalCollection spc = new SimplePrincipalCollection(); spc.add(user.getName(), "unit"); spc.add(user, "unit"); - spc.add(new GroupNames(group, groups), "unit"); Subject subject = new Subject.Builder().authenticated(true).principals(spc).buildSubject(); shiro.setSubject(subject); + + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(group); + builder.add(groups); + when(groupCollector.collect(user.getName())).thenReturn(builder.build()); } /** diff --git a/scm-webapp/src/test/java/sonia/scm/security/DefaultRealmTest.java b/scm-webapp/src/test/java/sonia/scm/security/DefaultRealmTest.java index b6fea9e897..ba23411b36 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/DefaultRealmTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/DefaultRealmTest.java @@ -36,8 +36,6 @@ package sonia.scm.security; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.collect.Collections2; -import com.google.common.collect.Lists; - import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.DisabledAccountException; @@ -45,43 +43,44 @@ import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.DefaultPasswordService; -import org.apache.shiro.crypto.hash.DefaultHashService; -import org.apache.shiro.subject.PrincipalCollection; -import org.apache.shiro.subject.SimplePrincipalCollection; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -import sonia.scm.group.Group; -import sonia.scm.group.GroupDAO; -import sonia.scm.group.GroupNames; -import sonia.scm.user.User; -import sonia.scm.user.UserDAO; -import sonia.scm.user.UserTestData; - -import static org.hamcrest.Matchers.*; - -import static org.junit.Assert.*; - -import static org.mockito.Mockito.*; - -//~--- JDK imports ------------------------------------------------------------ - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.authz.permission.WildcardPermissionResolver; +import org.apache.shiro.crypto.hash.DefaultHashService; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.group.GroupDAO; +import sonia.scm.user.User; +import sonia.scm.user.UserDAO; +import sonia.scm.user.UserTestData; + +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +//~--- JDK imports ------------------------------------------------------------ /** * @@ -206,32 +205,6 @@ public class DefaultRealmTest ); } - /** - * Method description - * - */ - @Test - public void testGroupCollection() - { - User user = UserTestData.createTrillian(); - //J- - List groups = Lists.newArrayList( - new Group(DefaultRealm.REALM, "scm", user.getName()), - new Group(DefaultRealm.REALM, "developers", "perfect") - ); - //J+ - - when(groupDAO.getAll()).thenReturn(groups); - - UsernamePasswordToken token = daoUser(user, "secret"); - AuthenticationInfo info = realm.getAuthenticationInfo(token); - GroupNames groupNames = info.getPrincipals().oneByType(GroupNames.class); - - assertNotNull(groupNames); - assertThat(groupNames.getCollection(), hasSize(2)); - assertThat(groupNames, hasItems("scm", GroupNames.AUTHENTICATED)); - } - /** * Method description * @@ -251,12 +224,6 @@ public class DefaultRealmTest assertThat(collection.getRealmNames(), hasSize(1)); assertThat(collection.getRealmNames(), hasItem(DefaultRealm.REALM)); assertEquals(user, collection.oneByType(User.class)); - - GroupNames groups = collection.oneByType(GroupNames.class); - - assertNotNull(groups); - assertThat(groups.getCollection(), hasSize(1)); - assertThat(groups.getCollection(), hasItem(GroupNames.AUTHENTICATED)); } /** diff --git a/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenBuilderTest.java b/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenBuilderTest.java index e2117235fc..7a8c0ef169 100644 --- a/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenBuilderTest.java +++ b/scm-webapp/src/test/java/sonia/scm/security/JwtAccessTokenBuilderTest.java @@ -36,27 +36,25 @@ import com.github.sdorra.shiro.SubjectAware; import com.google.common.collect.Sets; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.PrincipalCollection; -import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ThreadContext; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.group.ExternalGroupNames; -import java.util.Arrays; -import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.when; import static sonia.scm.security.SecureKeyTestUtil.createSecureKey; /** @@ -137,7 +135,6 @@ public class JwtAccessTokenBuilderTest { .issuer("https://www.scm-manager.org") .expiresIn(5, TimeUnit.SECONDS) .custom("a", "b") - .groups("one", "two", "three") .scope(Scope.valueOf("repo:*")) .build(); @@ -154,36 +151,6 @@ public class JwtAccessTokenBuilderTest { assertClaims(new JwtAccessToken(claims, compact)); } - @Test - public void testWithExternalGroups() { - applyExternalGroupsToSubject(true, "external"); - JwtAccessToken token = factory.create().subject("dent").build(); - assertArrayEquals(new String[]{"external"}, token.getCustom(JwtAccessToken.GROUPS_CLAIM_KEY).map(x -> (String[]) x).get()); - } - - @Test - public void testWithInternalGroups() { - applyExternalGroupsToSubject(false, "external"); - JwtAccessToken token = factory.create().subject("dent").build(); - assertFalse(token.getCustom(JwtAccessToken.GROUPS_CLAIM_KEY).isPresent()); - } - - private void applyExternalGroupsToSubject(boolean external, String... groups) { - Subject subject = spy(SecurityUtils.getSubject()); - when(subject.getPrincipals()).thenAnswer(invocation -> enrichWithGroups(invocation, groups, external)); - shiro.setSubject(subject); - } - - private Object enrichWithGroups(InvocationOnMock invocation, String[] groups, boolean external) throws Throwable { - PrincipalCollection principals = (PrincipalCollection) spy(invocation.callRealMethod()); - - List groupCollection = Arrays.asList(groups); - if (external) { - when(principals.oneByType(ExternalGroupNames.class)).thenReturn(new ExternalGroupNames(groupCollection)); - } - return principals; - } - private void assertClaims(JwtAccessToken token){ assertThat(token.getId(), not(isEmptyOrNullString())); assertNotNull( token.getIssuedAt() ); @@ -194,6 +161,5 @@ public class JwtAccessTokenBuilderTest { assertEquals(token.getIssuer().get(), "https://www.scm-manager.org"); assertEquals("b", token.getCustom("a").get()); assertEquals("[\"repo:*\"]", token.getScope().toString()); - assertThat(token.getGroups(), containsInAnyOrder("one", "two", "three")); } }