package org.sakaiproject.site.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.authz.api.AuthzGroup; import org.sakaiproject.authz.api.GroupNotDefinedException; import org.sakaiproject.authz.api.Member; import org.sakaiproject.authz.api.Role; import org.sakaiproject.authz.cover.AuthzGroupService; import org.sakaiproject.component.cover.ComponentManager; import org.sakaiproject.coursemanagement.api.CourseOffering; import org.sakaiproject.coursemanagement.api.CourseSet; import org.sakaiproject.coursemanagement.api.Enrollment; import org.sakaiproject.coursemanagement.api.EnrollmentSet; import org.sakaiproject.coursemanagement.api.Membership; import org.sakaiproject.coursemanagement.api.Section; import org.sakaiproject.coursemanagement.api.exception.IdNotFoundException; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.user.api.User; import org.sakaiproject.user.api.UserNotDefinedException; import org.sakaiproject.user.cover.UserDirectoryService; import org.sakaiproject.util.ResourceLoader; public class SiteParticipantHelper { /** Our log (commons). */ private static Log M_log = LogFactory.getLog(SiteParticipantHelper.class); private static String NULL_STRING = ""; private static org.sakaiproject.authz.api.GroupProvider groupProvider = (org.sakaiproject.authz.api.GroupProvider) ComponentManager .get(org.sakaiproject.authz.api.GroupProvider.class); private static org.sakaiproject.authz.api.AuthzGroupService authzGroupService = (org.sakaiproject.authz.api.AuthzGroupService) ComponentManager .get(org.sakaiproject.authz.api.AuthzGroupService.class); private static org.sakaiproject.coursemanagement.api.CourseManagementService cms = (org.sakaiproject.coursemanagement.api.CourseManagementService) ComponentManager .get(org.sakaiproject.coursemanagement.api.CourseManagementService.class); private static org.sakaiproject.user.api.ContextualUserDisplayService cus = (org.sakaiproject.user.api.ContextualUserDisplayService) ComponentManager .get(org.sakaiproject.user.api.ContextualUserDisplayService.class); private static org.sakaiproject.authz.api.SecurityService securityService = (org.sakaiproject.authz.api.SecurityService) ComponentManager .get(org.sakaiproject.authz.api.SecurityService.class ); private static org.sakaiproject.component.api.ServerConfigurationService scs = (org.sakaiproject.component.api.ServerConfigurationService) ComponentManager .get(org.sakaiproject.component.api.ServerConfigurationService.class); private static String showOrphanedMembers = scs.getString("site.setup.showOrphanedMembers", "admins"); private static ResourceLoader rb = new ResourceLoader("UserDirectoryProvider"); // SAK-23257: restrict the roles available for participants private static final String SAK_PROP_RESTRICTED_ROLES = "sitemanage.addParticipants.restrictedRoles"; /** * Add participant from provider-defined enrollment set * @param participants * @param realm * @param providerCourseEid * @param enrollmentSet */ public static void addParticipantsFromEnrollmentSet(Map participantsMap, AuthzGroup realm, String providerCourseEid, EnrollmentSet enrollmentSet, String sectionTitle) { boolean refreshed = false; if (enrollmentSet != null) { Set enrollments = cms.getEnrollments(enrollmentSet.getEid()); if (enrollments != null) { Map<String, User> eidToUserMap = getEidUserMapFromCollection(enrollments); for (Iterator eIterator = enrollments.iterator();eIterator.hasNext();) { Enrollment e = (Enrollment) eIterator.next(); // ignore the dropped enrollments if(e.isDropped()){ continue; } try { User user = eidToUserMap.get(e.getUserId()); if (user == null) { throw new UserNotDefinedException(e.getUserId()); } String userId = user.getId(); Member member = realm.getMember(userId); // this person is in the cm, so they should be in the realm // force a refresh. Only do this once, since a refresh should get everyone // it would be nice for AuthzGroupService to expose refresh, but a save // will do it if (member == null && !refreshed) { try { // do it only once refreshed = true; // refresh the realm AuthzGroup realmEdit = AuthzGroupService.getAuthzGroup(realm.getId()); AuthzGroupService.save(realmEdit); // refetch updated realm realm = AuthzGroupService.getAuthzGroup(realm.getId()); member = realm.getMember(userId); } catch (Exception exc) { M_log.warn("SiteParticipantHelper.addParticipantsFromEnrollment " + exc.getMessage()); } } if (member != null) { try { // get or add provided participant Participant participant; if (participantsMap.containsKey(userId)) { participant = (Participant) participantsMap.get(userId); //does this section contain the eid already if (!participant.getSectionEidList().contains(sectionTitle)) { participant.addSectionEidToList(sectionTitle); } if (e.getCredits() != null && e.getCredits().length() >0) { participant.credits = participant.credits.concat(", <br />" + e.getCredits()); } } else { participant = new Participant(); participant.credits = e.getCredits() != null?e.getCredits():""; participant.name = user.getSortName(); if (member.isProvided()) { participant.providerRole = member.getRole()!=null?member.getRole().getId():""; participant.removeable = false; } else { participant.providerRole=""; participant.removeable = true; } // get contextual user display id String regId = cus != null ? cus.getUserDisplayId(user, "Site Info"):""; participant.regId = regId != null?regId:""; participant.role = member.getRole()!=null?member.getRole().getId():""; participant.addSectionEidToList(sectionTitle); participant.uniqname = userId; participant.active = member.isActive(); } participantsMap.put(userId, participant); } catch (Exception ee) { M_log.warn("SiteParticipantHelper.addParticipantsFromEnrollmentSet: " + ee.getMessage() + " user id = " + userId, ee); } } } catch (UserNotDefinedException exception) { // deal with missing user quietly without throwing a // warning message M_log.warn("SiteParticipantHelper.addParticipantsFromEnrollmentSet: " + exception.getMessage() + " user id = " + e.getUserId()); } } } } } /** * Collect all member users data in one call * * @param memberships * @return */ public static Map<String, User> getEidUserMapFromCollection(Collection<Object> cObjects) { Set<String> rvEids = new HashSet<String>(); for (Object cObject : cObjects) { if (cObject instanceof Enrollment) { rvEids.add(((Enrollment) cObject).getUserId()); } else if (cObject instanceof Membership) { rvEids.add(((Membership) cObject).getUserId()); } else if (cObject instanceof Member) { rvEids.add(((Member) cObject).getUserEid()); } } Map<String, User> eidToUserMap = new HashMap<String, User>(); List<User> rvUsers = UserDirectoryService.getUsersByEids(rvEids); for (User user : rvUsers) { eidToUserMap.put(user.getEid(), user); } return eidToUserMap; } /** * Add participant from provider-defined membership set * @param participants * @param realm * @param providerCourseEid * @param memberships */ public static void addParticipantsFromMemberships(Map participantsMap, AuthzGroup realm, Set memberships, String sectionTitle) { boolean refreshed = false; if (memberships != null) { Map<String, User> eidToUserMap = getEidUserMapFromCollection(memberships); for (Iterator<Membership> mIterator = memberships.iterator();mIterator.hasNext();) { Membership m = (Membership) mIterator.next(); try { User user = eidToUserMap.get(m.getUserId()); if (user == null) { throw new UserNotDefinedException(m.getUserId()); } String userId = user.getId(); Member member = realm.getMember(userId); // this person is in the cm, so they should be in the realm // force a refresh. Only do this once, since a refresh should get everyone // it would be nice for AuthzGroupService to expose refresh, but a save // will do it if (member == null && !refreshed) { try { // do it only once refreshed = true; // refresh the realm AuthzGroup realmEdit = AuthzGroupService.getAuthzGroup(realm.getId()); AuthzGroupService.save(realmEdit); // refetch updated realm realm = AuthzGroupService.getAuthzGroup(realm.getId()); member = realm.getMember(userId); } catch (Exception exc) { M_log.warn("SiteParticipantHelper:addParticipantsFromMembership " + exc.getMessage()); } } if (member != null) { // get or add provided participant Participant participant; if (participantsMap.containsKey(userId)) { participant = (Participant) participantsMap.get(userId); participant.addSectionEidToList(sectionTitle); } else { participant = new Participant(); participant.credits = ""; participant.name = user.getSortName(); if (member.isProvided()) { participant.providerRole = member.getRole()!=null?member.getRole().getId():""; participant.removeable = false; } participant.regId = ""; participant.role = member.getRole()!=null?member.getRole().getId():""; participant.addSectionEidToList(sectionTitle); participant.uniqname = userId; participant.active=member.isActive(); } participantsMap.put(userId, participant); } } catch (UserNotDefinedException exception) { // deal with missing user quietly without throwing a // warning message M_log.debug("SiteParticipantHelper:addParticipantsFromMemberships: user not defined id = " + m.getUserId()); } } } } /** * add participant from member list defined in realm * @param participantsMap * @param grants */ private static void addParticipantsFromMembers(Map<String, Participant> participantsMap, Set grants, String realmId) { // get all user info once Map<String, User> eidToUserMap = getEidUserMapFromCollection(grants); boolean refreshed = false; for (Iterator<Member> i = grants.iterator(); i.hasNext();) { Member g = (Member) i.next(); try { User user = eidToUserMap.get(g.getUserEid()); if (user == null) { throw new UserNotDefinedException(g.getUserEid()); } String userId = user.getId(); if (!participantsMap.containsKey(userId)) { // we should have seen all provided users by now. If any // are left, they are out of date. Refresh the realm // but also skip the users. Otherwise if the owner submits // the screen, they get created. if (g.isProvided()) { if (!refreshed) { refreshed = true; try { // refresh the realm AuthzGroup realmEdit = AuthzGroupService.getAuthzGroup(realmId); AuthzGroupService.save(realmEdit); } catch (Exception exc) { M_log.warn("SiteParticipantHelper:addParticipantsFromMembers " + exc.getMessage()); } } continue; } Participant participant; if (participantsMap.containsKey(userId)) { participant = (Participant) participantsMap.get(userId); } else { participant = new Participant(); } participant.name = user.getSortName(); participant.uniqname = userId; participant.role = g.getRole()!=null?g.getRole().getId():""; participant.removeable = true; participant.active = g.isActive(); participantsMap.put(userId, participant); } } catch (UserNotDefinedException e) { if (("admins".equals(showOrphanedMembers) && securityService.isSuperUser()) || ("maintainers".equals(showOrphanedMembers))) { // add non-registered participant String userId = g.getUserId(); Participant participant = new Participant(); participant.name = makeUserDisplayName(userId); participant.uniqname = userId; participant.role = g.getRole() != null ? g.getRole().getId() :""; participant.removeable = true; participant.active = g.isActive(); participantsMap.put(userId, participant); } if (M_log.isDebugEnabled()) { M_log.debug("SiteParticipantHelper:addParticipantsFromMembers: user not defined "+ g.getUserEid()); } } } } /** * getExternalRealmId * */ private static String getExternalRealmId(String siteId) { String realmId = SiteService.siteReference(siteId); String rv = null; try { AuthzGroup realm = AuthzGroupService.getAuthzGroup(realmId); rv = realm.getProviderGroupId(); } catch (GroupNotDefinedException e) { M_log.warn("SiteParticipantHelper.getExternalRealmId: site realm not found " + realmId); } return rv; } // getExternalRealmId /** * getProviderCourseList a course site/realm id in one of three formats, for * a single section, for multiple sections of the same course, or for a * cross-listing having multiple courses. getProviderCourseList parses a * realm id into year, term, campus_code, catalog_nbr, section components. * * @param id * is a String representation of the course realm id (external * id). */ public static List<String> getProviderCourseList(String siteId) { String id = getExternalRealmId(siteId); Vector<String> rv = new Vector<String>(); if (id == null || NULL_STRING.equals(id) ) { return rv; } // Break Provider Id into course id parts String[] courseIds = groupProvider.unpackId(id); // Iterate through course ids for (int i=0; i<courseIds.length; i++) { String courseId = (String) courseIds[i]; rv.add(courseId); } return rv; } // getProviderCourseList public static Collection<Participant> prepareParticipants(String siteId, List<String> providerCourseList) { String realmId = SiteService.siteReference(siteId); Map<String, Participant> participantsMap = new ConcurrentHashMap<String, Participant>(); try { AuthzGroup realm = authzGroupService.getAuthzGroup(realmId); realm.getProviderGroupId(); // iterate through the provider list first for (Iterator<String> i=providerCourseList.iterator(); i.hasNext();) { String providerCourseEid = (String) i.next(); try { Section section = cms.getSection(providerCourseEid); if (section != null) { String sectionTitle = section.getTitle(); // in case of Section eid EnrollmentSet enrollmentSet = section.getEnrollmentSet(); addParticipantsFromEnrollmentSet(participantsMap, realm, providerCourseEid, enrollmentSet, sectionTitle); // Include official instructors of record for the enrollment set. addOfficialInstructorOfRecord(participantsMap, realm, sectionTitle, enrollmentSet); // add memberships Set memberships = cms.getSectionMemberships(providerCourseEid); if (memberships != null && memberships.size() > 0) { addParticipantsFromMemberships(participantsMap, realm, memberships, sectionTitle); } // now look or the not-included member from CourseOffering object CourseOffering co = cms.getCourseOffering(section.getCourseOfferingEid()); if (co != null) { Set<Membership> coMemberships = cms.getCourseOfferingMemberships(section.getCourseOfferingEid()); if (coMemberships != null && coMemberships.size() > 0) { addParticipantsFromMemberships(participantsMap, realm, coMemberships, co.getTitle()); } // now look or the not-included member from CourseSet object Set<String> cSetEids = co.getCourseSetEids(); if (cSetEids != null) { for(Iterator<String> cSetEidsIterator = cSetEids.iterator(); cSetEidsIterator.hasNext();) { String cSetEid = cSetEidsIterator.next(); CourseSet cSet = cms.getCourseSet(cSetEid); if (cSet != null) { Set<Membership> cSetMemberships = cms.getCourseSetMemberships(cSetEid); if (cSetMemberships != null && cSetMemberships.size() > 0) { addParticipantsFromMemberships(participantsMap, realm, cSetMemberships, cSet.getTitle()); } } } } } } } catch (IdNotFoundException e) { M_log.warn("SiteParticipantHelper.prepareParticipants: "+ e.getMessage() + " sectionId=" + providerCourseEid); } } // now for those not provided users Set<Member> grants = realm.getMembers(); if (grants != null && !grants.isEmpty()) { // add participant from member defined in realm addParticipantsFromMembers(participantsMap, grants, realmId); } } catch (GroupNotDefinedException ee) { M_log.warn("SiteParticipantHelper.prepareParticipants: IdUnusedException " + realmId); } return participantsMap.values(); } private static void addOfficialInstructorOfRecord(Map<String, Participant> participantsMap, AuthzGroup realm, String sectionTitle, EnrollmentSet enrollmentSet) { if (enrollmentSet != null) { Set<String>instructorEids = cms.getInstructorsOfRecordIds(enrollmentSet.getEid()); if ((instructorEids != null) && (instructorEids.size() > 0)) { for (String userEid : instructorEids) { // This logic is copied-and-pasted from addParticipantsFromMemberships // and really should be in a shared method, but refactoring would make // it harder to merge changes. try { User user = UserDirectoryService.getUserByEid(userEid); String userId = user.getId(); Member member = realm.getMember(userId); if (member != null) { // get or add provided participant Participant participant; if (participantsMap.containsKey(userId)) { participant = (Participant) participantsMap.get(userId); if (!participant.section.contains(sectionTitle)) { participant.section = participant.section.concat(", <br />" + sectionTitle); } } else { participant = new Participant(); participant.credits = ""; participant.name = user.getSortName(); if (member.isProvided()) { participant.providerRole = member.getRole()!=null?member.getRole().getId():""; participant.removeable = false; } participant.regId = ""; participant.removeable = false; participant.role = member.getRole()!=null?member.getRole().getId():""; participant.section = sectionTitle; participant.uniqname = userId; } participantsMap.put(userId, participant); } } catch (UserNotDefinedException exception) { // deal with missing user quietly without throwing a // warning message M_log.warn(exception.getMessage()); } } } } } /** * Get a list of restricted roles, taking into account the current site type * * @author bjones86 * @param siteType * the current site's type * @return a list of restricted role IDs for the given site type */ public static Set<String> getRestrictedRoles( String siteType ) { // Add all root level restricted roles Set<String> retVal = new HashSet<String>(); retVal.addAll(Arrays.asList(ArrayUtils.nullToEmpty(scs.getStrings(SAK_PROP_RESTRICTED_ROLES)))); // Add all site type specficic restricted roles if(siteType != null && !"".equals(siteType)) { retVal.addAll(Arrays.asList(ArrayUtils.nullToEmpty(scs.getStrings(SAK_PROP_RESTRICTED_ROLES + "." + siteType)))); } return retVal; } /** * Get a list of the 'allowed roles', taking into account the current site type * and the list of restricted roles defined in sakai.properties. * If the properties are not found, just return all the roles. * If the user is an admin, return all the roles. * * @author bjones86 - SAK-23257 * * @param siteType * the current site's type * @return A list of 'allowed' role objects for the given site type */ public static List<Role> getAllowedRoles( String siteType, List<Role> allRolesForSiteType ) { List<Role> retVal = new ArrayList<Role>(); if (siteType == null) { siteType = ""; } // Get all the restricted roles for this site type, as well as all restricted roles at the top level (restricted for all site types) Set<String> restrictedRoles = getRestrictedRoles(siteType); // Loop through all the roles for this site type for( Role role : allRolesForSiteType ) { // If the user is an admin, or if the properties weren't found (empty set), just add the role to the list if( securityService.isSuperUser() || restrictedRoles.isEmpty() ) { retVal.add( role ); } // Otherwise, only add the role to the list of 'allowed' roles if it's not present in the set of 'restricted' roles else { if( !restrictedRoles.contains( role.getId() ) && !restrictedRoles.contains( role.getId().toLowerCase() ) ) { retVal.add( role ); } } } return retVal; } public static List<Role> getAllowedRoles( String siteType, Set<Role> allRolesForSiteType ) { List<Role> list = new ArrayList<Role>(allRolesForSiteType.size()); list.addAll(allRolesForSiteType); return getAllowedRoles( siteType, list ); } public static String makeUserDisplayName( String userId ) { String userDisplayName = NULL_STRING; userDisplayName = rb.getFormattedMessage("udp.unregistered", userId); return userDisplayName; } }