package edu.harvard.iq.dataverse.authorization.groups; import edu.harvard.iq.dataverse.DvObject; import edu.harvard.iq.dataverse.RoleAssigneeServiceBean; import edu.harvard.iq.dataverse.authorization.RoleAssignee; import edu.harvard.iq.dataverse.authorization.groups.impl.builtin.BuiltInGroupsProvider; import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroup; import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupProvider; import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupServiceBean; import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.IpGroupProvider; import edu.harvard.iq.dataverse.authorization.groups.impl.ipaddress.IpGroupsServiceBean; import edu.harvard.iq.dataverse.authorization.groups.impl.shib.ShibGroupProvider; import edu.harvard.iq.dataverse.authorization.groups.impl.shib.ShibGroupServiceBean; import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser; import edu.harvard.iq.dataverse.engine.command.DataverseRequest; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import static java.util.stream.Collectors.toSet; import java.util.stream.Stream; import javax.annotation.PostConstruct; import javax.ejb.EJB; import javax.ejb.Stateless; import javax.inject.Named; /** * * @author michael */ @Stateless @Named public class GroupServiceBean { private static final Logger logger = Logger.getLogger(GroupServiceBean.class.getName()); @EJB IpGroupsServiceBean ipGroupsService; @EJB ShibGroupServiceBean shibGroupService; @EJB ExplicitGroupServiceBean explicitGroupService; private final Map<String, GroupProvider> groupProviders = new HashMap<>(); private IpGroupProvider ipGroupProvider; private ShibGroupProvider shibGroupProvider; private ExplicitGroupProvider explicitGroupProvider; @EJB RoleAssigneeServiceBean roleAssigneeSvc; @PostConstruct public void setup() { addGroupProvider( BuiltInGroupsProvider.get() ); addGroupProvider( ipGroupProvider = new IpGroupProvider(ipGroupsService) ); addGroupProvider( shibGroupProvider = new ShibGroupProvider(shibGroupService) ); addGroupProvider( explicitGroupProvider = explicitGroupService.getProvider() ); Logger.getLogger(GroupServiceBean.class.getName()).log(Level.INFO, null, "PostConstruct group service call"); } public Group getGroup( String groupAlias ) { String[] comps = groupAlias.split( Group.PATH_SEPARATOR, 2 ); GroupProvider gp = groupProviders.get( comps[0] ); if ( gp == null ) { logger.log(Level.WARNING, "Cannot find group provider with alias {0}", comps[0]); return null; } return gp.get( comps[1] ); } public IpGroupProvider getIpGroupProvider() { return ipGroupProvider; } public ShibGroupProvider getShibGroupProvider() { return shibGroupProvider; } /** * Finds all the groups {@code req} is part of in {@code dvo}'s context. * Recurses upwards in {@link ExplicitGroup}s, as needed. * @param req The request whose group memberships we seek. * @param dvo The {@link DvObject} we determining the context fo the membership. * @return The groups {@code req} is part of under {@code dvo}. */ public Set<Group> groupsFor( DataverseRequest req, DvObject dvo ) { return groupTransitiveClosure( groupProviders.values().stream() .flatMap(gp->(Stream<Group>)gp.groupsFor(req, dvo).stream()) .collect(toSet()), dvo); } /** * All the groups a Role assignee belongs to. Does not take request-level groups * (such as IPGroups) into account. * @param ra * @param dvo * @return */ public Set<Group> groupsFor( RoleAssignee ra, DvObject dvo ) { return groupTransitiveClosure( groupProviders.values().stream() .flatMap(gp->(Stream<Group>)gp.groupsFor(ra, dvo).stream()) .collect( toSet() ), dvo); } /** * groupsFor( RoleAssignee ra, DvObject dvo ) doesn't really get *all* the * groups a Role assignee belongs to as advertised but this method comes * closer. * * @param au An AuthenticatedUser. * @return As many groups as we can find for the AuthenticatedUser. * * @deprecated Does not look into IP Groups. Use {@link #groupsFor(edu.harvard.iq.dataverse.engine.command.DataverseRequest)} */ @Deprecated public Set<Group> groupsFor(AuthenticatedUser au) { Set<Group> groups = new HashSet<>(); groups.addAll(groupsFor(au, null)); String identifier = au.getIdentifier(); if (identifier != null) { try { groups.addAll( explicitGroupService.findGroups(au) ); } catch (IndexOutOfBoundsException ex) { logger.log(Level.INFO, "Couldn''t trim first character (@ sign) from identifier: {0}", identifier); } } return groups; } public Set<Group> groupsFor( DataverseRequest dr ) { Set<Group> groups = new HashSet<>(); // get the global groups groups.addAll( groupsFor(dr,null) ); // add the explicit groups groups.addAll( explicitGroupService.findGroups(dr.getUser()) ); return groups; } /** * Collections of groups may include {@link ExplicitGroup}s, which have a * recursive structure (more precisely, a Composite Pattern}. This has many * advantages, but it makes answering the question "which groups are * contained in this group" non-trivial. This method deals with this issue by * providing a "flat list" of the groups contained in the groups at the * passed collection. * * The resultant stream is distinct - groups appear in it only once, even if * some of them are members of multiple groups. * * @param groups A collection of groups * @return A distinct stream of groups who are members of, or are * descendants of members of the groups in {@code groups}. */ public Stream<Group> flattenGroupsCollection( Collection<Group> groups ) { Stream.Builder<Group> out = Stream.builder(); groups.forEach( g -> { out.accept(g); if ( g instanceof ExplicitGroup ) { collectGroupContent((ExplicitGroup) g, out); } }); return out.build().distinct(); } private void collectGroupContent( ExplicitGroup eg, Stream.Builder<Group> out ) { eg.getContainedRoleAssgineeIdentifiers().stream() .map( idtf -> roleAssigneeSvc.getRoleAssignee(idtf) ) .filter( asn -> asn instanceof Group ) .forEach( group -> out.accept((Group)group) ); eg.getContainedExplicitGroups().forEach( meg -> { out.accept(meg); collectGroupContent(meg, out); }); } /** * Returns all the groups that are in, of are ancestors of a group in * the passed group collection. * * @param groups * @return {@code groups} and their ancestors. */ public Set<Group> collectAncestors( Collection<Group> groups ) { // Ancestors will be collected here. Set<Group> retVal = new HashSet<>(); // Groups whose ancestors were not collected yet. Set<Group> perimeter = new HashSet(groups); while ( ! perimeter.isEmpty() ) { Group next = perimeter.iterator().next(); retVal.add(next); perimeter.remove(next); explicitGroupService.findDirectlyContainingGroups(next).forEach( g -> { if ( ! retVal.contains(g) ) { perimeter.add( g ); } }); } return retVal; } /** * Given a set of groups and a DV object, return all the groups that are * reachable from the set. Effectively, if the initial set has an {@link ExplicitGroup}, * recursively add all the groups it contains. * * @param groups * @param dvo * @return All the groups included in the groups in {@code groups}. */ private Set<Group> groupTransitiveClosure(Set<Group> groups, DvObject dvo) { // now, get the explicit group transitive closure. Set<ExplicitGroup> perimeter = new HashSet<>(); Set<ExplicitGroup> visited = new HashSet<>(); groups.stream() .filter((g) -> ( g instanceof ExplicitGroup )) .forEachOrdered((g) -> perimeter.add((ExplicitGroup) g)); visited.addAll(perimeter); while ( ! perimeter.isEmpty() ) { ExplicitGroup g = perimeter.iterator().next(); perimeter.remove(g); groups.add(g); Set<ExplicitGroup> discovered = explicitGroupProvider.groupsFor(g, dvo); discovered.removeAll(visited); // Ideally the conjunction is always empty, as we don't allow cycles. // Still, coding defensively here, in case someone gets too // smart on the SQL console. perimeter.addAll(discovered); visited.addAll(discovered); } return groups; } public Set<Group> findGlobalGroups() { Set<Group> groups = new HashSet<>(); groupProviders.values().forEach( gp-> groups.addAll( gp.findGlobalGroups() )); return groups; } private void addGroupProvider( GroupProvider gp ) { groupProviders.put( gp.getGroupProviderAlias(), gp ); } }