/**
* $Id: MembershipEntityProvider.java 130237 2013-10-08 15:25:40Z azeckoski@unicon.net $
* $URL: https://source.sakaiproject.org/svn/entitybroker/trunk/core-providers/src/java/org/sakaiproject/entitybroker/providers/MembershipEntityProvider.java $
* ServerConfigEntityProvider.java - entity-broker - Jul 17, 2008 2:19:03 PM - azeckoski
**************************************************************************
* Copyright (c) 2008, 2009 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sakaiproject.entitybroker.providers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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 org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.authz.api.Member;
import org.sakaiproject.authz.api.Role;
import org.sakaiproject.email.api.EmailService;
import org.sakaiproject.entitybroker.EntityReference;
import org.sakaiproject.entitybroker.EntityView;
import org.sakaiproject.entitybroker.entityprovider.CoreEntityProvider;
import org.sakaiproject.entitybroker.entityprovider.annotations.EntityCustomAction;
import org.sakaiproject.entitybroker.entityprovider.capabilities.ActionsExecutable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.CollectionResolvable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.RESTful;
import org.sakaiproject.entitybroker.entityprovider.extension.ActionReturn;
import org.sakaiproject.entitybroker.entityprovider.extension.EntityData;
import org.sakaiproject.entitybroker.entityprovider.extension.Formats;
import org.sakaiproject.entitybroker.entityprovider.search.Order;
import org.sakaiproject.entitybroker.entityprovider.search.Restriction;
import org.sakaiproject.entitybroker.entityprovider.search.Search;
import org.sakaiproject.entitybroker.exception.EntityNotFoundException;
import org.sakaiproject.entitybroker.providers.model.EntityMember;
import org.sakaiproject.entitybroker.providers.model.EntityUser;
import org.sakaiproject.entitybroker.util.AbstractEntityProvider;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.exception.PermissionException;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.site.api.SiteService.SelectionType;
/**
* This provides access to memberships as entities
*
* @author Aaron Zeckoski (azeckoski @ gmail.com)
*/
public class MembershipEntityProvider extends AbstractEntityProvider implements CoreEntityProvider,
RESTful, ActionsExecutable {
private static Log log = LogFactory.getLog(MembershipEntityProvider.class);
private SiteService siteService;
public void setSiteService(SiteService siteService) {
this.siteService = siteService;
}
private UserEntityProvider userEntityProvider;
public void setUserEntityProvider(UserEntityProvider userEntityProvider) {
this.userEntityProvider = userEntityProvider;
}
private EmailService emailService;
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
public static String PREFIX = "membership";
public String getEntityPrefix() {
return PREFIX;
}
private static final String GROUP_PROP_WSETUP_CREATED = "group_prop_wsetup_created";
private static final String ADMIN_SITE_ID = "!admin";
/**
* SAKAI CONFIG
* False by default, no admin site changes allowed
*/
private static final String ADMIN_SITE_CHANGE_ALLOWED = "eb.membership.admin.site.changes.allowed";
private boolean allowAdminSiteChanges = false;
public void init() {
allowAdminSiteChanges = developerHelperService.getConfigurationSetting(ADMIN_SITE_CHANGE_ALLOWED, false);
}
/**
* join/site/siteId or join/siteId Handle the special case of joining a site, using normal
* create will not work
*/
@EntityCustomAction(action = "join", viewKey = EntityView.VIEW_NEW)
public boolean joinCurrentUserToSite(EntityView view, Map<String, Object> params) {
String siteId = view.getPathSegment(2);
if (siteId == null) {
siteId = (String) params.get("siteId");
} else if ("site".equals(siteId)) {
siteId = view.getPathSegment(3);
}
if (siteId == null) {
throw new IllegalArgumentException(
"siteId must be set in order join sites, set in params or in the URL /join/site/siteId");
}
checkSiteSecurity(siteId);
try {
siteService.join(siteId);
} catch (IdUnusedException e) {
throw new IllegalArgumentException("The siteId provided (" + siteId
+ ") could not be found: " + e, e);
} catch (PermissionException e) {
throw new SecurityException("The current user ("
+ developerHelperService.getCurrentUserId()
+ ") does not have permission to join site (" + siteId + "): " + e, e);
}
return true;
}
/**
* unjoin/site/siteId or unjoin/siteId Handle the special case of un-joining a site, using
* normal delete will not work
*/
@EntityCustomAction(action = "unjoin", viewKey = EntityView.VIEW_NEW)
public boolean unjoinCurrentUserFromSite(EntityView view, Map<String, Object> params) {
String siteId = view.getPathSegment(2);
if (siteId == null) {
siteId = (String) params.get("siteId");
} else if ("site".equals(siteId)) {
siteId = view.getPathSegment(3);
}
if (siteId == null) {
throw new IllegalArgumentException(
"siteId must be set in order to unjoin sites, set in params or in the URL /unjoin/site/siteId");
}
checkSiteSecurity(siteId);
try {
siteService.unjoin(siteId);
} catch (IdUnusedException e) {
throw new IllegalArgumentException("The siteId provided (" + siteId
+ ") could not be found: " + e, e);
} catch (PermissionException e) {
throw new SecurityException("The current user ("
+ developerHelperService.getCurrentUserId()
+ ") does not have permission to join site (" + siteId + "): " + e, e);
}
return true;
}
/**
* Handle the special needs of UX site membership settings, either getting the current list of
* site memberships via a GET request, or creating a new batch of site memberships via a POST
* request. In the case of a POST, special HTTP response headers will be used to communicate
* success or warning conditions to the client.
*/
@EntityCustomAction(action = "site", viewKey = "")
public ActionReturn handleSiteMemberships(EntityView view, Map<String, Object> params) {
if (log.isDebugEnabled())
log.debug("handleSiteMemberships method=" + view.getMethod() + ", params=" + params);
String siteId = view.getPathSegment(2);
if (siteId == null) {
siteId = (String) params.get("siteId");
if (siteId == null) {
throw new IllegalArgumentException(
"siteId must be set in order to get site memberships, set in params or in the URL /membership/site/siteId");
}
}
String locationReference = "/site/" + siteId;
Map<String, String> extraResponseHeaders = null;
if (EntityView.Method.POST.name().equals(view.getMethod())) {
extraResponseHeaders = createBatchMemberships(view, params, locationReference);
}
List<EntityData> l = getEntities(new EntityReference(PREFIX, ""), new Search(
CollectionResolvable.SEARCH_LOCATION_REFERENCE, locationReference));
ActionReturn actionReturn = new ActionReturn(l, Formats.JSON);
if ((extraResponseHeaders != null) && !extraResponseHeaders.isEmpty()) {
actionReturn.setHeaders(extraResponseHeaders);
}
return actionReturn;
}
/**
* Add members to a site.
*
* @param view
* @param params
* request parameters including a list of userSearchValues
* @param locationReference
* @return headers containing success or warning messages for the client
*/
public Map<String, String> createBatchMemberships(EntityView view, Map<String, Object> params,
String locationReference) {
SiteGroup sg = findLocationByReference(locationReference);
String roleId = (String) params.get("memberRole");
String notificationMessage = (String) params.get("notificationMessage");
if ((notificationMessage != null) && (notificationMessage.trim().length() == 0)) {
notificationMessage = null;
}
boolean active = true;
Map<String, String> responseHeaders = new HashMap<String, String>();
Set<EntityUser> users = new HashSet<EntityUser>();
Set<String> valuesNotFound = new HashSet<String>();
Set<String> valuesAlreadyMembers = new HashSet<String>();
List<String> userSearchValues = getListFromValue(params.get("userSearchValues"));
for (String userSearchValue : userSearchValues) {
EntityUser user = userEntityProvider.findUserFromSearchValue(userSearchValue);
if (user != null) {
if (sg.site.getUserRole(user.getId()) != null) {
valuesAlreadyMembers.add(userSearchValue);
} else {
users.add(user);
}
} else {
valuesNotFound.add(userSearchValue);
}
}
if (!users.isEmpty()) {
String currentUserEmail = userEntityProvider.getCurrentUser(null).getEmail();
for (EntityUser user : users) {
sg.site.addMember(user.getId(), roleId, active, false);
if (notificationMessage != null) {
/**
* TODO Should the From address be the site contact or the "setup.request" Sakai
* property? TODO We need to retrieve a localized message title and additional
* body (if any) instead of hard-coding it. See the new Email Template Service
* for a likely approach.
*/
emailService.send(currentUserEmail, user.getEmail(),
"New Site Membership Notification", notificationMessage, null, null,
null);
}
}
saveSiteMembership(sg.site);
responseHeaders.put("x-success-count", String.valueOf(users.size()));
}
if (!valuesNotFound.isEmpty()) {
Iterator<String> listIter = valuesNotFound.iterator();
StringBuilder listString = new StringBuilder(listIter.next());
while (listIter.hasNext()) {
listString.append(", ").append(listIter.next());
}
responseHeaders.put("x-warning-not-found", listString.toString());
}
if (!valuesAlreadyMembers.isEmpty()) {
Iterator<String> listIter = valuesAlreadyMembers.iterator();
StringBuilder listString = new StringBuilder(listIter.next());
while (listIter.hasNext()) {
listString.append(", ").append(listIter.next());
}
responseHeaders.put("x-warning-already-members", listString.toString());
}
return responseHeaders;
}
@EntityCustomAction(action = "group", viewKey = "")
public List<EntityData> getGroupMemberships(EntityView view, Map<String, Object> params) {
String groupId = view.getPathSegment(2);
List<EntityData> ed = null;
if (EntityView.Method.GET.name().equals(view.getMethod())) {
// GET /direct/membership/group/groupid - gets current membership for the given groupid
if (groupId == null) {
groupId = (String) params.get("groupId");
if (groupId == null) {
throw new IllegalArgumentException(
"groupId must be set in order to get group memberships, set in params or in the URL /membership/group/groupId");
}
}
ed = getEntities(new EntityReference(PREFIX, ""), new Search(
CollectionResolvable.SEARCH_LOCATION_REFERENCE, "/group/" + groupId));
} else if (EntityView.Method.POST.name().equals(view.getMethod())) {
// POST /direct/membership/group/groupid - update the membership for the given groupid
String action = params.get("action") != null ? params.get("action").toString() : null;
if (action == null || "".equals(action)) {
throw new IllegalArgumentException(
"A parameter named 'action' needs to be specified. 'action' can be update, add or remove. Cannot edit group:"
+ groupId);
}
List<String> userIds = params.get("userIds") != null ? Arrays.asList(params.get(
"userIds").toString().split(",")) : new ArrayList<String>();
if (userIds.size() <= 0) {
throw new IllegalArgumentException(
"A list of user ids needs to be specified as a parameter named 'userIds'. Cannot edit group:"
+ groupId);
}
SiteGroup siteGroup = findLocationByReference("/group/" + groupId);
Site site = siteGroup.site;
Group group = siteGroup.group;
if (site == null) {
throw new IllegalArgumentException("The site for the group (" + groupId
+ ") could not be found.");
}
if (group == null) {
throw new IllegalArgumentException("The group provided (" + groupId
+ ") could not be found.");
}
checkGroupType(group);
if (!siteService.allowUpdateSite(site.getId())) {
throw new SecurityException("This site (" + site.getReference()
+ ") cannot be updated by the current user.");
}
if ("add".equals(action)) {
// add the list to the existing membership
for (String user : userIds) {
String userId = userEntityProvider.findAndCheckUserId(null, user.trim());
if (userId == null) {
log.warn("Unable to add user ("+user+") to group ("+group.getId()+") in site ("+site.getId()+"), could not find user record by id or eid");
continue;
}
Member m = site.getMember(userId);
if (m == null) {
log.warn("Unable to add user ("+user+") to group ("+group.getId()+") in site ("+site.getId()+"), user is not a member of the site (and must be)");
continue;
}
Role role = m.getRole();
if (group.getMember(userId) == null && (role != null && role.getId() != null)) {
// Every user added via this EB is defined as non-provided
group.addMember(userId, role.getId(), m != null ? m.isActive() : true,
false);
}
}
} else if ("update".equals(action)) {
if (!siteService.allowUpdateGroupMembership(site.getId())) {
throw new SecurityException("This group (" + groupId + ") in site ("
+ site.getId() + ") cannot be updated by the current user.");
}
// replace the current membership with the provided list
group.removeMembers();
for (String user : userIds) {
String userId = userEntityProvider.findAndCheckUserId(null, user.trim());
if (userId == null) {
log.warn("Unable to update user ("+user+") in group ("+group.getId()+") in site ("+site.getId()+"), could not find user record by id or eid");
continue;
}
Member m = site.getMember(userId);
Role role = m.getRole();
if (group.getMember(userId) == null && (role != null && role.getId() != null)) {
// Every user added via this EB is defined as non-provided
group.addMember(userId, role.getId(), m != null ? m.isActive() : true,
false);
}
}
} else if ("remove".equals(action)) {
// remove the list from the existing membership
for (String userId : userIds) {
userId = userEntityProvider.findAndCheckUserId(null, userId.trim());
if (userId == null) {
log.warn("Unable to remove user ("+userId+") from group ("+group.getId()+") in site ("+site.getId()+"), could not find user record by id or eid");
continue;
}
group.removeMember(userId);
}
} else {
throw new IllegalArgumentException(
"A valid value for the parameter named 'action' needs to be specified. 'action' can be update, add or remove. Cannot edit group:"
+ groupId);
}
// save group
try {
siteService.save(site);
} catch (IdUnusedException e) {
throw new IllegalArgumentException("Cannot find site with given id: "
+ site.getId() + ":" + e.getMessage(), e);
} catch (PermissionException e) {
throw new SecurityException(
"Current user does not have permission to save this group:" + groupId
+ " to site:" + site.getId());
}
return null;
}
return ed;
}
public boolean entityExists(String id) {
if (id == null) {
return false;
}
if ("".equals(id)) {
return true;
}
String[] parts = EntityMember.parseId(id);
if (parts != null) {
// TODO check it later when there is an efficient way
return true;
}
return false;
}
public Object getEntity(EntityReference ref) {
if (ref.getId() == null) {
return new EntityMember();
}
String mid = ref.getId();
String[] parts = EntityMember.parseId(mid);
if (parts == null) {
throw new IllegalArgumentException(
"Invalid membership id ("
+ mid
+ "), should be formed like so: 'userId::site:siteId' or 'userId::group:groupId");
}
EntityMember member = getMember(parts[0], parts[1]);
if (member == null) {
throw new IllegalArgumentException("Cannot find membership with id: " + mid);
}
return member;
}
/**
* Gets the list of all memberships for the current user if no params provided, otherwise gets
* memberships in a specified location or for a specified user
*/
public List<EntityData> getEntities(EntityReference ref, Search search) {
String currentUserId = developerHelperService.getCurrentUserId();
String userId = null;
String locationReference = null;
String roleId = null;
boolean includeSites = true;
boolean includeGroups = false;
if (search == null) {
search = new Search();
}
if (!search.isEmpty()) {
// process the search
roleId = (String) search.getRestrictionValueByProperties(new String[] { "role",
"roleId" });
Restriction userRes = search
.getRestrictionByProperty(CollectionResolvable.SEARCH_USER_REFERENCE);
if (userRes != null) {
String userRef = userRes.getStringValue();
userId = EntityReference.getIdFromRef(userRef);
}
Restriction locRes = search
.getRestrictionByProperty(CollectionResolvable.SEARCH_LOCATION_REFERENCE);
if (locRes != null) {
locationReference = locRes.getStringValue();
}
Restriction incSites = search.getRestrictionByProperty("includeSites");
if (incSites != null) {
includeSites = incSites.getBooleanValue();
}
Restriction incGroups = search.getRestrictionByProperty("includeGroups");
if (incGroups != null) {
includeGroups = incGroups.getBooleanValue();
}
}
if (locationReference == null && userId == null) {
// if these are both null then we default to getting memberships for the current user
if (currentUserId != null) {
userId = currentUserId;
}
}
if (locationReference == null && userId == null) {
// fail if there is still nothing to output
throw new IllegalArgumentException(
"There must be a current user logged in "
+ "OR you must provide a search with the following restrictions (getting all is not supported): "
+ "siteId, locationReference, groupId AND (optionally) roleId OR userReference, userId, user");
}
List<EntityMember> members = new ArrayList<EntityMember>();
boolean findByLocation = false;
if (locationReference != null) {
// get membership for a location
findByLocation = true;
members = getMembers(locationReference);
} else {
// get membership for
if (!includeGroups && !includeSites) {
throw new IllegalArgumentException(
"includesSites and includesGroups cannot both be false");
}
// find memberships by userId
userId = userEntityProvider.findAndCheckUserId(userId, null);
//SAK-22396 if the user is unknown this will be null
if (userId == null) {
throw new IllegalArgumentException("unable to find user with id ("+userId+")");
}
boolean userCurrent = userId.equals(currentUserId);
if (!userCurrent && !developerHelperService.isUserAdmin(currentUserId)) {
throw new SecurityException(
"Only admin can access other user memberships, current user ("
+ currentUserId + ") cannot access ref: " + userId);
}
// Is there a faster way to do this? I really truly hope so -AZ
try {
if (!userCurrent) {
developerHelperService.setCurrentUser("/user/" + userId);
}
List<Site> sites = siteService.getSites(SelectionType.ACCESS, null, null, null,
null, null);
for (Site site : sites) {
Member sm = site.getMember(userId);
if (sm != null) {
if (includeSites) {
members.add(new EntityMember(sm, site.getReference(), null));
}
// also check the groups
if (includeGroups) {
Collection<Group> groups = site.getGroups();
for (Group group : groups) {
Member gm = group.getMember(userId);
if (gm != null) {
members.add(new EntityMember(gm, group.getReference(), null));
}
}
}
}
}
} finally {
if (!userCurrent) {
developerHelperService.restoreCurrentUser();
}
}
}
ArrayList<EntityMember> sortedMembers = new ArrayList<EntityMember>();
int count = 0;
for (EntityMember em : members) {
// filter out users and roles
if (count < search.getStart()) {
continue;
} else if (search.getLimit() > 0 && count > search.getLimit()) {
break; // no more, limit reached
} else {
// between the start and limit
if (roleId != null) {
if (!roleId.equals(em.getMemberRole())) {
continue;
}
}
if (findByLocation) {
if (userId != null) {
if (!userId.equals(em.getUserId())) {
continue;
}
}
}
sortedMembers.add(em);
}
count++;
}
// handle the sorting
Comparator<EntityMember> memberComparator = new EntityMember.MemberSortName(); // default by
// sortname
if (search.getOrders().length > 0) {
Order order = search.getOrders()[0]; // only one sort allowed
if ("email".equals(order.getProperty())) {
memberComparator = new EntityMember.MemberEmail();
} else if ("displayName".equals(order.getProperty())) {
memberComparator = new EntityMember.MemberDisplayName();
} else if ("lastLogin".equals(order.getProperty())) {
memberComparator = new EntityMember.MemberLastLogin();
}
}
Collections.sort(sortedMembers, memberComparator);
// TODO reverse sorting?
// now we put the members into entity data objects
ArrayList<EntityData> l = new ArrayList<EntityData>();
for (EntityMember em : sortedMembers) {
EntityData ed = new EntityData(new EntityReference(PREFIX, em.getId()), null, em);
l.add(ed);
}
return l;
}
public String createEntity(EntityReference ref, Object entity, Map<String, Object> params) {
SiteGroup sg = null;
String roleId = null;
String userId = null;
boolean active = true;
if (entity.getClass().isAssignableFrom(Member.class)) {
// if someone passes in a Member
Member member = (Member) entity;
String locationReference = (String) params.get("locationReference");
if (locationReference == null) {
throw new IllegalArgumentException(
"Cannot create/update a membership entity from Member without a locationReference in the params");
}
sg = findLocationByReference(locationReference);
roleId = member.getRole().getId();
userId = userEntityProvider.findAndCheckUserId(member.getUserId(), member.getUserEid());
active = member.isActive();
} else if (entity.getClass().isAssignableFrom(EntityMember.class)) {
// if they instead pass in the EntitySite object
EntityMember em = (EntityMember) entity;
sg = findLocationByReference(em.getLocationReference());
roleId = em.getMemberRole();
if ((em.getUserId() != null) || (em.getUserEid() != null)) {
userId = userEntityProvider.findAndCheckUserId(em.getUserId(), em.getUserEid());
}
active = em.isActive();
} else {
throw new IllegalArgumentException(
"Invalid entity for create/update, must be Member or EntityMember object");
}
if (roleId == null || "".equals(roleId)) {
roleId = sg.site.getJoinerRole();
}
checkSiteSecurity(sg.site.getId());
// check for a batch add
String[] userIds = checkForBatch(params, userId);
// now add all the memberships
String memberId = "";
String currentUserId = developerHelperService.getCurrentUserId();
for (int i = 0; i < userIds.length; i++) {
if (sg.group == null) {
// site only
if (userIds[i].equals(currentUserId) && sg.site.isJoinable()) {
try {
siteService.join(sg.site.getId());
} catch (IdUnusedException e) {
throw new IllegalArgumentException("Invalid site: " + sg.site.getId() + ":"
+ e.getMessage(), e);
} catch (PermissionException e) {
throw new SecurityException("Current user not allowed to join site: "
+ sg.site.getId() + ":" + e.getMessage(), e);
}
} else {
sg.site.addMember(userIds[i], roleId, active, false);
saveSiteMembership(sg.site);
}
} else {
// group and site
sg.group.addMember(userIds[i], roleId, active, false);
saveGroupMembership(sg.site, sg.group);
}
if (i == 0) {
EntityMember em = new EntityMember(userIds[0], sg.locationReference, roleId,
active, null);
memberId = em.getId();
}
}
if (userIds.length > 1) {
log.info("Batch add memberships: siteId="
+ ((sg.site == null) ? "none" : sg.site.getId()) + ",groupId="
+ ((sg.group == null) ? "none" : sg.group.getId()) + ",userIds="
+ Search.arrayToString(userIds));
memberId = "batch:" + memberId;
}
return memberId;
}
public Object getSampleEntity() {
return new EntityMember();
}
public void updateEntity(EntityReference ref, Object entity, Map<String, Object> params) {
// same operation for updating memberships, maybe we should check if they exist?
createEntity(ref, entity, params);
}
public void deleteEntity(EntityReference ref, Map<String, Object> params) {
String mid = ref.getId();
String[] parts = EntityMember.parseId(mid);
if (parts == null) {
throw new IllegalArgumentException(
"Invalid membership id ("
+ mid
+ "), should be formed like so: 'userId::site:siteId' or 'userId::group:groupId");
}
String userId = parts[0];
SiteGroup sg = findLocationByReference(parts[1]);
// check for a batch
String[] userIds = checkForBatch(params, userId);
for (int i = 0; i < userIds.length; i++) {
if (sg.group == null) {
// site only
sg.site.removeMember(userIds[i]);
saveSiteMembership(sg.site);
} else {
// group and site
sg.group.removeMember(userIds[i]);
saveGroupMembership(sg.site, sg.group);
}
}
if (userIds.length > 1) {
log.info("Batch remove memberships: siteId="
+ ((sg.site == null) ? "none" : sg.site.getId()) + ",groupId="
+ ((sg.group == null) ? "none" : sg.group.getId()) + ",userIds="
+ Search.arrayToString(userIds));
}
}
public String[] getHandledOutputFormats() {
return new String[] { Formats.HTML, Formats.XML, Formats.JSON, Formats.FORM };
}
public String[] getHandledInputFormats() {
return new String[] { Formats.HTML, Formats.XML, Formats.JSON };
}
public EntityMember getMember(String userId, String locationReference) {
EntityMember em = null;
Member member = null;
SiteGroup sg = findLocationByReference(locationReference);
String currentUserId = developerHelperService.getCurrentUserId();
if (!userId.equals(currentUserId)) {
isAllowedAccessMembers(sg.site);
}
if (sg.group == null) {
// site only
member = sg.site.getMember(userId);
} else {
// group and site
member = sg.group.getMember(userId);
}
if (member != null) {
EntityUser eu = userEntityProvider.getUserById(userId);
em = new EntityMember(member, sg.locationReference, eu);
}
return em;
}
/**
* @param locationReference
* a site ref with an optional group ref (can look like this:
* /site/siteid/group/groupId)
* @return the list of memberships for the given location and role
*/
public List<EntityMember> getMembers(String locationReference) {
ArrayList<EntityMember> l = new ArrayList<EntityMember>();
Set<Member> members = null;
SiteGroup sg;
try {
sg = findLocationByReference(locationReference);
} catch (IllegalArgumentException e) {
throw new EntityNotFoundException("Could not find the location based on the ref ("+locationReference+"): " + e, locationReference);
}
isAllowedAccessMembers(sg.site);
if (sg.group == null) {
// site only
members = sg.site.getMembers();
} else {
// group and site
members = sg.group.getMembers();
}
// filter out possible invalid/orphaned users (SAK-22396, SAK-17498, SAK-23863)
for (Member member : members) {
EntityUser eu = userEntityProvider.getUserById(member.getUserId());
if (eu != null) {
EntityMember em = new EntityMember(member, sg.locationReference, eu);
l.add(em);
}
}
return l;
}
/**
* Find a site (and optionally group) by reference
*
* @param locationReference
* @return a Site and optional group
* @throws IllegalArgumentException
* if they cannot be found for this ref
*/
public SiteGroup findLocationByReference(String locationReference) {
SiteGroup holder = new SiteGroup(locationReference);
if (locationReference.contains("/group/")) {
// group membership
String groupId = EntityReference.getIdFromRefByKey(locationReference, "group");
if (groupId == null || "".equals(groupId)) {
throw new IllegalArgumentException(
"locationReferences for groups must be structured like this: /site/siteid/group/groupId or /group/groupId, could not find group in: "
+ locationReference);
}
locationReference = "/group/" + groupId;
Group group = siteService.findGroup(groupId);
// an invalid group ID might be passed which results in a null here
if (group == null) {
throw new IllegalArgumentException("No group found for id: "+groupId);
}
Site site = group.getContainingSite();
holder.locationReference = locationReference;
holder.group = group;
holder.site = site;
} else if (locationReference.contains("/site/")) {
// site membership
String siteId = EntityReference.getIdFromRefByKey(locationReference, "site");
Site site = getSiteById(siteId);
holder.site = site;
} else {
throw new IllegalArgumentException(
"Do not know how to handle this location reference (" + locationReference
+ "), only can handle site and group references");
}
if (holder.site == null) {
throw new IllegalArgumentException(
"Could not find a site/group with the given reference: " + locationReference);
}
return holder;
}
/**
* Look for a batch membership operation
*
* @param params
* @param userId
* @return
*/
protected String[] checkForBatch(Map<String, Object> params, String userId) {
HashSet<String> userIds = new HashSet<String>();
if (userId != null) {
userIds.add(userId);
}
if (params != null) {
List<String> batchUserIds = getListFromValue(params.get("userIds"));
for (String batchUserId : batchUserIds) {
String uid = userEntityProvider.findAndCheckUserId(batchUserId, null);
if (uid != null) {
userIds.add(uid);
}
}
}
if (log.isDebugEnabled())
log.debug("Received userIds=" + userIds);
return userIds.toArray(new String[userIds.size()]);
}
protected List<String> getListFromValue(Object paramValue) {
List<String> stringList = new ArrayList<String>();
if (paramValue != null) {
if (paramValue.getClass().isArray()) {
stringList = Arrays.asList((String[]) paramValue);
} else if (paramValue instanceof String) {
stringList.add((String) paramValue);
}
}
return stringList;
}
protected String makeRoleId(String currentRoleId, Site site) {
String roleId = currentRoleId;
if (roleId == null || "".equals(roleId)) {
roleId = site.getJoinerRole();
}
return roleId;
}
/**
* @param site
* @param group
*/
protected void saveGroupMembership(Site site, Group group) {
try {
siteService.saveGroupMembership(site);
} catch (IdUnusedException e) {
throw new IllegalArgumentException("Invalid site: " + site.getId() + ":"
+ e.getMessage(), e);
} catch (PermissionException e) {
throw new SecurityException("Current user ("
+ developerHelperService.getCurrentUserId()
+ ") not allowed to update site group memberships in group: " + group.getId()
+ " :" + e.getMessage() + ":" + e.getCause(), e);
}
}
/**
* @param site
*/
protected void saveSiteMembership(Site site) {
checkSiteSecurity(site.getId());
try {
siteService.saveSiteMembership(site);
} catch (IdUnusedException e) {
throw new IllegalArgumentException("Invalid site: " + site.getId() + ":"
+ e.getMessage(), e);
} catch (PermissionException e) {
throw new SecurityException("Current user ("
+ developerHelperService.getCurrentUserId()
+ ") not allowed to update site memberships in site: " + site.getId() + " :"
+ e.getMessage() + ":" + e.getCause(), e);
}
}
protected Site getSiteById(String siteId) {
Site site;
try {
site = siteService.getSite(siteId);
} catch (IdUnusedException e) {
throw new IllegalArgumentException("Cannot find site by siteId: " + siteId, e);
}
return site;
}
/**
* @param site
* the site to check perms in
* @return true if the current user can view this site
* @throws SecurityException
* if not allowed
*/
protected boolean isAllowedAccessMembers(Site site) {
// check if the current user can access this
String userReference = developerHelperService.getCurrentUserReference();
if (userReference == null) {
throw new SecurityException("Anonymous users may not view memberships in ("
+ site.getReference() + ")");
} else {
String siteId = site.getId();
if (siteService.allowViewRoster(siteId)) {
return true;
} else {
throw new SecurityException("Memberships in this site (" + site.getReference()
+ ") are not accessible for the current user: " + userReference);
}
}
}
/**
* This contains the site and optionally group for a given reference
*
* @author Aaron Zeckoski (azeckoski @ gmail.com)
*/
public static class SiteGroup {
public Site site;
public Group group;
public String locationReference;
public SiteGroup(String locationReference) {
this.locationReference = locationReference;
}
}
/**
* This adds the users to the group provided in the site provided
*
* @param site
* The site which the group belongs
* @param group
* The group to add the users to
* @param userIds
* The list of Uuids to use
* NOTE: not used?
*/
protected void addUsersToGroup(Site site, Group group, List<String> userIds) {
for (String user : userIds) {
String userId = user.trim();
Role role = site.getUserRole(userId);
Member m = site.getMember(userId);
if (group.getUserRole(userId) == null && role.getId() != null) {
// Every user added via this EB is defined as non-provided
group.addMember(userId, role.getId(), m != null ? m.isActive() : true, false);
}
}
}
/**
* Only handle Site Info type groups.
*
* @param group
* @throws IllegalArgumentException
* if NOT a Site Info type group
*/
private void checkGroupType(Group group) {
if (group != null) {
try {
if (!group.getProperties().getBooleanProperty(GROUP_PROP_WSETUP_CREATED)) {
throw new IllegalArgumentException(
"This type of group (Section Info group) should not be edited by this entity provider. Only Site info groups are allowed.");
}
} catch (Exception e) {
throw new IllegalArgumentException(
"This type of group (Section Info group) should not be edited by this entity provider. Only Site info groups are allowed.");
}
}
}
/**
* SAK-20828 handle low hanging CSRF blocking
* @param siteId the sakai site id
* @throws SecurityException if this site cannot be updated via provider
*/
private void checkSiteSecurity(String siteId) {
if (!allowAdminSiteChanges && ADMIN_SITE_ID.equals(siteId)) {
throw new SecurityException("Admin site membership changes are disabled for security protection against CSRF, you must use the sakai admin UI or enable changes in your sakai config file using "+ADMIN_SITE_CHANGE_ALLOWED+"=true");
}
}
}