/**
* Copyright (c) 2008-2010 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.osedu.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.roster.impl;
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 org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.api.privacy.PrivacyManager;
import org.sakaiproject.authz.api.AuthzGroup;
import org.sakaiproject.authz.api.FunctionManager;
import org.sakaiproject.authz.api.GroupProvider;
import org.sakaiproject.authz.api.Member;
import org.sakaiproject.authz.api.Role;
import org.sakaiproject.authz.api.SecurityService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.coursemanagement.api.CourseManagementService;
import org.sakaiproject.coursemanagement.api.Enrollment;
import org.sakaiproject.coursemanagement.api.EnrollmentSet;
import org.sakaiproject.coursemanagement.api.Section;
import org.sakaiproject.exception.IdUnusedException;
import org.sakaiproject.profile2.logic.ProfileConnectionsLogic;
import org.sakaiproject.roster.api.RosterEnrollment;
import org.sakaiproject.roster.api.RosterFunctions;
import org.sakaiproject.roster.api.RosterGroup;
import org.sakaiproject.roster.api.RosterMember;
import org.sakaiproject.roster.api.RosterSite;
import org.sakaiproject.roster.api.SakaiProxy;
import org.sakaiproject.site.api.Group;
import org.sakaiproject.site.api.Site;
import org.sakaiproject.site.api.SiteService;
import org.sakaiproject.tool.api.SessionManager;
import org.sakaiproject.tool.api.ToolManager;
import org.sakaiproject.user.api.User;
import org.sakaiproject.user.api.UserDirectoryService;
import org.sakaiproject.user.api.UserNotDefinedException;
import org.sakaiproject.util.ResourceLoader;
/**
* <code>SakaiProxy</code> acts as a proxy between Roster and Sakai components.
*
* @author Daniel Robinson (d.b.robinson@lancaster.ac.uk)
* @author Adrian Fish (a.fish@lancaster.ac.uk)
*/
public class SakaiProxyImpl implements SakaiProxy {
private static final Log log = LogFactory.getLog(SakaiProxyImpl.class);
private CourseManagementService courseManagementService;
private FunctionManager functionManager = null;
private PrivacyManager privacyManager = null;
private ProfileConnectionsLogic connectionsLogic;
private SecurityService securityService = null;
private ServerConfigurationService serverConfigurationService = null;
private SessionManager sessionManager = null;
private SiteService siteService;
private ToolManager toolManager = null;
private UserDirectoryService userDirectoryService = null;
private static SakaiProxyImpl instance = null;
/**
* Returns an instance of <code>SakaiProxyImpl</code>.
*
* @return an instance of <code>SakaiProxyImpl</code>.
*/
public static SakaiProxyImpl instance() {
if (null == instance) {
instance = new SakaiProxyImpl();
}
return instance;
}
/**
* Creates a new instance of <code>SakaiProxyImpl</code>
*/
private SakaiProxyImpl() {
org.sakaiproject.component.api.ComponentManager componentManager =
org.sakaiproject.component.cover.ComponentManager.getInstance();
boolean connectionsApi;
try {
// check for Profile2 1.4.x connections API
Class.forName("org.sakaiproject.profile2.logic.ProfileConnectionsLogic");
connectionsApi = true;
} catch (ClassNotFoundException e) {
connectionsApi = false;
}
log.info("Profile2 >=1.4.x connections API found: " + connectionsApi);
if (true == connectionsApi) {
connectionsLogic = (ProfileConnectionsLogic) componentManager.get(ProfileConnectionsLogic.class);
}
courseManagementService = (CourseManagementService) componentManager.get(CourseManagementService.class);
functionManager = (FunctionManager) componentManager.get(FunctionManager.class);
privacyManager = (PrivacyManager) componentManager.get(PrivacyManager.class);
securityService = (SecurityService) componentManager.get(SecurityService.class);
serverConfigurationService = (ServerConfigurationService) componentManager.get(ServerConfigurationService.class);
sessionManager = (SessionManager) componentManager.get(SessionManager.class);
siteService = (SiteService) componentManager.get(SiteService.class);
toolManager = (ToolManager) componentManager.get(ToolManager.class);
userDirectoryService = (UserDirectoryService) componentManager.get(UserDirectoryService.class);
init();
log.info("org.sakaiproject.roster.api.SakaiProxy initialized");
}
private void init() {
log.info("org.sakaiproject.roster.api.SakaiProxy init()");
List<String> registered = functionManager.getRegisteredFunctions();
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_EXPORT)) {
functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_EXPORT, true);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWALL)) {
functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWALL, true);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN)) {
functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN, true);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWGROUP)) {
functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, true);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS)) {
functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS, true);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE)) {
functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWPROFILE, true);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL)) {
functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL, true);
}
if (!registered.contains(RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO)) {
functionManager.registerFunction(RosterFunctions.ROSTER_FUNCTION_VIEWOFFICIALPHOTO, true);
}
}
/**
* {@inheritDoc}
*/
public boolean isSuperUser() {
return securityService.isSuperUser();
}
private Site getSite(String siteId) {
try {
return siteService.getSite(siteId);
} catch (IdUnusedException e) {
log.warn("site not found: " + e.getId());
return null;
}
}
/**
* {@inheritDoc}
*/
public String getCurrentUserId() {
return sessionManager.getCurrentSessionUserId();
}
/**
* {@inheritDoc}
*/
public String getCurrentSiteId() {
return toolManager.getCurrentPlacement().getContext();
}
/**
* {@inheritDoc}
*/
public Integer getDefaultRosterState() {
return serverConfigurationService.getInt("roster.defaultState",
DEFAULT_ROSTER_STATE);
}
/**
* {@inheritDoc}
*/
public String getDefaultRosterStateString() {
Integer defaultRosterState = getDefaultRosterState();
if (defaultRosterState > -1 && defaultRosterState < ROSTER_STATES.length - 1) {
return ROSTER_STATES[defaultRosterState];
} else {
return ROSTER_STATES[DEFAULT_ROSTER_STATE];
}
}
/**
* {@inheritDoc}
*/
public String getDefaultSortColumn() {
return serverConfigurationService
.getString("roster.defaultSortColumn", DEFAULT_SORT_COLUMN);
}
/**
* {@inheritDoc}
*/
public Boolean getFirstNameLastName() {
return serverConfigurationService.getBoolean(
"roster.display.firstNameLastName", DEFAULT_FIRST_NAME_LAST_NAME);
}
/**
* {@inheritDoc}
*/
public Boolean getHideSingleGroupFilter() {
return serverConfigurationService.getBoolean(
"roster.display.hideSingleGroupFilter",
DEFAULT_HIDE_SINGLE_GROUP_FILTER);
}
/**
* {@inheritDoc}
*/
public Boolean getViewEmail() {
return getViewEmail(getCurrentSiteId());
}
/**
* {@inheritDoc}
*/
public Boolean getViewEmail(String siteId) {
//To view emails it first needs to be enabled in sakai.properties and the user must have the permission.
if(serverConfigurationService.getBoolean("roster_view_email",DEFAULT_VIEW_EMAIL)) {
return hasUserSitePermission(getCurrentUserId(), RosterFunctions.ROSTER_FUNCTION_VIEWEMAIL, siteId);
}
return false;
}
/**
* {@inheritDoc}
*/
public Boolean getViewUserDisplayId() {
return serverConfigurationService.getBoolean(
"roster.display.userDisplayId", DEFAULT_VIEW_USER_DISPLAY_ID);
}
/**
* {@inheritDoc}
*/
public List<RosterMember> getSiteMembership(String siteId, boolean includeConnectionStatus) {
return getMembership(siteId, null, includeConnectionStatus);
}
/**
* {@inheritDoc}
*/
public List<RosterMember> getGroupMembership(String siteId, String groupId) {
return getMembership(siteId, groupId, false);
}
private List<RosterMember> getMembership(String siteId, String groupId,
boolean includeConnectionStatus) {
String userId = getCurrentUserId();
List<RosterMember> rosterMembers = new ArrayList<RosterMember>();
Site site = null;
try {
site = siteService.getSite(siteId);
} catch (IdUnusedException e) {
log.warn("site not found: " + e.getId());
}
if (null == site) {
return null;
}
// permissions are handled inside this method call
Set<Member> membership = getFilteredMembers(groupId, userId, site);
if (null == membership) {
return null;
}
for (Member member : membership) {
try {
RosterMember rosterMember =
getRosterMember(member, site, includeConnectionStatus, userId);
rosterMembers.add(rosterMember);
} catch (UserNotDefinedException e) {
log.warn("user not found: " + e.getId());
}
}
if (rosterMembers.size() == 0) {
return null;
}
return rosterMembers;
}
private Map<String, RosterMember> getMembershipMapped(String siteId,
String groupId, boolean filtered) {
Map<String, RosterMember> rosterMembers = new HashMap<String, RosterMember>();
Site site = null;
try {
site = siteService.getSite(siteId);
} catch (IdUnusedException e) {
log.warn("site not found: " + e.getId());
}
if (null == site) {
return null;
}
String userId = getCurrentUserId();
// permissions are handled inside this method call
Set<Member> membership = null;
if (true == filtered) {
membership = getFilteredMembers(groupId, userId, site);
} else {
membership = getUnfilteredMembers(groupId, userId, site);
}
if (null == membership) {
return null;
}
for (Member member : membership) {
try {
RosterMember rosterMember = getRosterMember(member, site, false, userId);
rosterMembers.put(rosterMember.getEid(), rosterMember);
} catch (UserNotDefinedException e) {
log.warn("user not found: " + e.getId());
}
}
return rosterMembers;
}
private Set<Member> getFilteredMembers(String groupId,
String currentUserId, Site site) {
Set<Member> membership = new HashSet<Member>();
if (isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWALL, site.getReference())) {
if (null == groupId) {
// get all members
membership.addAll(filterHiddenMembers(site.getMembers(),
currentUserId, site.getId(), site));
} else if (null != site.getGroup(groupId)) {
// get all members of requested groupId
membership.addAll(filterHiddenMembers(site.getGroup(groupId)
.getMembers(), currentUserId, site.getId(), site
.getGroup(groupId)));
} else {
// assume invalid groupId specified
return null;
}
} else {
if (null == groupId) {
// get all members of groups current user is allowed to view
for (Group group : site.getGroups()) {
if (isAllowed(currentUserId,
RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, group.getReference())) {
membership.addAll(filterHiddenMembers(group
.getMembers(), currentUserId, site.getId(),
group));
}
}
} else if (null != site.getGroup(groupId)) {
// get all members of requested groupId if current user is
// member
if (isAllowed(currentUserId,
RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, site
.getGroup(groupId).getReference())) {
membership.addAll(filterHiddenMembers(site
.getGroup(groupId).getMembers(), currentUserId,
site.getId(), site.getGroup(groupId)));
}
} else {
// assume invalid groupId specified or user not member
return null;
}
}
if(log.isDebugEnabled()) log.debug("membership.size(): " + membership.size());
//remove duplicates. Yes, its a Set but there can be dupes because its storing objects and from multiple groups.
Set<String> check = new HashSet<String>();
Set<Member> cleanedMembers = new HashSet<Member>();
for (Member m : membership) {
if(check.add(m.getUserId())) {
cleanedMembers.add(m);
}
}
if(log.isDebugEnabled()) log.debug("cleanedMembers.size(): " + cleanedMembers.size());
return cleanedMembers;
}
@SuppressWarnings("unchecked")
private Set<Member> filterHiddenMembers(Set<Member> membership,
String currentUserId, String siteId, AuthzGroup authzGroup) {
if(log.isDebugEnabled()) log.debug("filterHiddenMembers");
if (isAllowed(currentUserId,
RosterFunctions.ROSTER_FUNCTION_VIEWHIDDEN, authzGroup.getReference())) {
if(log.isDebugEnabled()) log.debug("permission to view all, including hidden");
return membership;
}
Set<Member> filteredMembership = new HashSet<Member>();
Set<String> userIds = new HashSet<String>();
for (Member member : membership) {
userIds.add(member.getUserEid());
}
Set<String> hiddenUserIds = privacyManager.findHidden(
"/site/" + siteId, userIds);
//get the list of visible roles, optional config.
//if set, the only users visible in the tool will be those with their role defined in this list
String[] visibleRoles = serverConfigurationService.getStrings("roster2.visibleroles");
boolean filterRoles = ArrayUtils.isNotEmpty(visibleRoles);
if(log.isDebugEnabled()) log.debug("visibleRoles: " + ArrayUtils.toString(visibleRoles));
if(log.isDebugEnabled()) log.debug("filterRoles: " + filterRoles);
// determine filtered membership
for (Member member : membership) {
// skip if privacy restricted
if (hiddenUserIds.contains(member.getUserEid())) {
continue;
}
// now filter out users based on their role
if(filterRoles) {
String memberRoleId = member.getRole().getId();
if(ArrayUtils.contains(visibleRoles, memberRoleId)){
filteredMembership.add(member);
if(log.isDebugEnabled()) log.debug("Filter added: " + member.getUserEid());
}
} else {
if(log.isDebugEnabled()) log.debug("Added: " + member.getUserEid());
filteredMembership.add(member);
}
}
if(log.isDebugEnabled()) log.debug("filteredMembership.size(): " + filteredMembership.size());
return filteredMembership;
}
private Set<Member> getUnfilteredMembers(String groupId,
String currentUserId, Site site) {
Set<Member> membership = new HashSet<Member>();
if (null == groupId) {
// get all members
membership.addAll(site.getMembers());
} else if (null != site.getGroup(groupId)) {
// get all members of requested groupId
membership.addAll(site.getGroup(groupId)
.getMembers());
} else {
// assume invalid groupId specified
return null;
}
return membership;
}
private RosterMember getRosterMember(Member member, Site site,
boolean includeConnectionStatus, String currentUserId) throws UserNotDefinedException {
String userId = member.getUserId();
User user = userDirectoryService.getUser(userId);
RosterMember rosterMember = new RosterMember(userId);
rosterMember.setEid(user.getEid());
rosterMember.setDisplayId(member.getUserDisplayId());
rosterMember.setRole(member.getRole().getId());
rosterMember.setEmail(user.getEmail());
rosterMember.setDisplayName(user.getDisplayName());
rosterMember.setSortName(user.getSortName());
Collection<Group> groups = site.getGroupsWithMember(userId);
Iterator<Group> groupIterator = groups.iterator();
while (groupIterator.hasNext()) {
Group group = groupIterator.next();
rosterMember.addGroup(group.getId(), group.getTitle());
}
if (true == includeConnectionStatus && connectionsLogic != null) {
rosterMember.setConnectionStatus(connectionsLogic
.getConnectionStatus(currentUserId, userId));
}
return rosterMember;
}
/**
* {@inheritDoc}
*/
public List<RosterMember> getEnrollmentMembership(String siteId,
String enrollmentSetId) {
Site site = null;
try {
site = siteService.getSite(siteId);
} catch (IdUnusedException e) {
log.warn("site not found: " + e.getId());
}
if (null == site) {
return null;
}
if (!isAllowed(getCurrentUserId(),
RosterFunctions.ROSTER_FUNCTION_VIEWENROLLMENTSTATUS, site.getReference())) {
return null;
}
EnrollmentSet enrollmentSet = courseManagementService
.getEnrollmentSet(enrollmentSetId);
if (null == enrollmentSet) {
return null;
}
Map<String, String> statusCodes = courseManagementService
.getEnrollmentStatusDescriptions(new ResourceLoader()
.getLocale());
Map<String, RosterMember> membership = getMembershipMapped(siteId,
null, false);
List<RosterMember> enrolledMembers = new ArrayList<RosterMember>();
for (Enrollment enrollment : courseManagementService
.getEnrollments(enrollmentSet.getEid())) {
RosterMember member = membership.get(enrollment.getUserId());
member.setCredits(enrollment.getCredits());
member.setEnrollmentStatus(statusCodes.get(enrollment.getEnrollmentStatus()));
enrolledMembers.add(member);
}
if (0 == enrolledMembers.size()) {
// to avoid IndexOutOfBoundsException in EB code
return null;
}
return enrolledMembers;
}
/**
* {@inheritDoc}
*/
public RosterSite getRosterSite(String siteId) {
String currentUserId = getCurrentUserId();
if (null == currentUserId) {
if(log.isDebugEnabled()) log.debug("No currentUserId. Returning null");
return null;
}
if(log.isDebugEnabled()) log.debug("currentUserId: " + currentUserId);
Site site = getSite(siteId);
if (null == site) {
if(log.isDebugEnabled()) log.debug("No site. Returning null");
return null;
}
if(log.isDebugEnabled()) log.debug("site: " + site.getId());
if(log.isDebugEnabled()) log.debug("Checking permissions for site: " + site.getReference());
// for DelegatedAccess to work, you must use the SecurityService.unlock method of checking permissions.
if(!isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWALL, site.getReference())) {
if(log.isDebugEnabled()) log.debug("roster.viewallmembers = false");
if(!isAllowed(currentUserId, RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, site.getReference())) {
if(log.isDebugEnabled()) log.debug("roster.viewgroup = false");
return null;
} else {
if(log.isDebugEnabled()) log.debug("roster.viewgroup = true. Access ok.");
}
} else {
if(log.isDebugEnabled()) log.debug("roster.viewallmembers = true. Access ok.");
}
RosterSite rosterSite = new RosterSite(site.getId());
rosterSite.setTitle(site.getTitle());
List<RosterGroup> siteGroups = getViewableSiteGroups(currentUserId,
site);
if (0 == siteGroups.size()) {
// to avoid IndexOutOfBoundsException in EB code
rosterSite.setSiteGroups(null);
} else {
rosterSite.setSiteGroups(siteGroups);
}
List<String> userRoles = new ArrayList<String>();
for (Role role : site.getRoles()) {
userRoles.add(role.getId());
}
if (0 == userRoles.size()) {
// to avoid IndexOutOfBoundsException in EB code
rosterSite.setUserRoles(null);
} else {
rosterSite.setUserRoles(userRoles);
}
GroupProvider groupProvider = (GroupProvider) ComponentManager
.get(GroupProvider.class);
Map<String, String> statusCodes = courseManagementService
.getEnrollmentStatusDescriptions(new ResourceLoader()
.getLocale());
rosterSite.setEnrollmentStatusDescriptions(new ArrayList<String>(
statusCodes.values()));
if (null == groupProvider) {
log.warn("no group provider installed");
// to avoid IndexOutOfBoundsException in EB code
rosterSite.setSiteEnrollmentSets(null);
return rosterSite;
}
List<RosterEnrollment> siteEnrollmentSets = getEnrollmentSets(siteId,
groupProvider);
if (0 == siteEnrollmentSets.size()) {
// to avoid IndexOutOfBoundsException in EB code
rosterSite.setSiteEnrollmentSets(null);
} else {
rosterSite.setSiteEnrollmentSets(siteEnrollmentSets);
}
return rosterSite;
}
private List<RosterGroup> getViewableSiteGroups(String currentUserId,
Site site) {
List<RosterGroup> siteGroups = new ArrayList<RosterGroup>();
boolean viewAll = isAllowed(currentUserId,
RosterFunctions.ROSTER_FUNCTION_VIEWALL, site);
for (Group group : site.getGroups()) {
if (viewAll
|| isAllowed(currentUserId,
RosterFunctions.ROSTER_FUNCTION_VIEWGROUP, group.getReference())) {
RosterGroup rosterGroup = new RosterGroup(group.getId());
rosterGroup.setTitle(group.getTitle());
List<String> userIds = new ArrayList<String>();
for (Member member : group.getMembers()) {
userIds.add(member.getUserId());
}
rosterGroup.setUserIds(userIds);
siteGroups.add(rosterGroup);
}
}
return siteGroups;
}
private List<RosterEnrollment> getEnrollmentSets(String siteId,
GroupProvider groupProvider) {
List<RosterEnrollment> siteEnrollmentSets = new ArrayList<RosterEnrollment>();
String[] sectionIds = groupProvider.unpackId(getSite(siteId)
.getProviderGroupId());
// avoid duplicates
List<String> enrollmentSetIdsProcessed = new ArrayList<String>();
for (String sectionId : sectionIds) {
Section section = courseManagementService.getSection(sectionId);
if (null == section) {
continue;
}
EnrollmentSet enrollmentSet = section.getEnrollmentSet();
if (null == enrollmentSet) {
continue;
}
if (enrollmentSetIdsProcessed.contains(enrollmentSet.getEid())) {
continue;
}
RosterEnrollment rosterEnrollmentSet = new RosterEnrollment(enrollmentSet.getEid());
rosterEnrollmentSet.setTitle(enrollmentSet.getTitle());
siteEnrollmentSets.add(rosterEnrollmentSet);
enrollmentSetIdsProcessed.add(enrollmentSet.getEid());
}
return siteEnrollmentSets;
}
private boolean isAllowed(String userId, String permision, AuthzGroup authzGroup) {
if (securityService.isSuperUser(userId)) {
return true;
}
return authzGroup.isAllowed(userId, permision);
}
/**
* Calls the SecurityService unlock method. This is the method you must use in order for Delegated Access to work.
* Note that the SecurityService automatically handles super users.
*
* @param userId user uuid
* @param permission permission to check for
* @param reference reference to entity. The getReference() method should get you out of trouble.
* @return true if user has permission, false otherwise
*/
private boolean isAllowed(String userId, String permission, String reference) {
return securityService.unlock(userId, permission, reference);
}
/**
* {@inheritDoc}
*/
public Boolean hasUserSitePermission(String userId, String permission, String siteId) {
Site site = getSite(siteId);
if (null == site) {
return false;
} else {
return isAllowed(userId, permission, site.getReference());
}
}
/**
* {@inheritDoc}
*/
public Boolean hasUserGroupPermission(String userId, String permission,
String siteId, String groupId) {
Site site = getSite(siteId);
if (null == site) {
return false;
} else {
if (null == site.getGroup(groupId)) {
return false;
} else {
return isAllowed(userId, permission, site.getGroup(groupId).getReference());
}
}
}
/**
* {@inheritDoc}
*/
public String getSakaiSkin() {
String skin = serverConfigurationService.getString("skin.default");
String siteSkin = siteService.getSiteSkin(getCurrentSiteId());
return siteSkin != null ? siteSkin : (skin != null ? skin : "default");
}
/**
* {@inheritDoc}
*/
public boolean isSiteMaintainer(String siteId) {
return hasUserSitePermission(getCurrentUserId(), SiteService.SECURE_UPDATE_SITE, siteId);
}
}