/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.wiki.user.script;
import java.util.ArrayList;
import java.util.Collection;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.xwiki.component.annotation.Component;
import org.xwiki.context.Execution;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.script.service.ScriptService;
import org.xwiki.security.authorization.AccessDeniedException;
import org.xwiki.security.authorization.AuthorizationManager;
import org.xwiki.security.authorization.Right;
import org.xwiki.wiki.descriptor.WikiDescriptorManager;
import org.xwiki.wiki.user.MemberCandidacy;
import org.xwiki.wiki.user.MembershipType;
import org.xwiki.wiki.user.UserScope;
import org.xwiki.wiki.user.WikiUserManager;
import org.xwiki.wiki.user.WikiUserManagerException;
import com.xpn.xwiki.XWikiContext;
/**
* Script service to manage how the user are supported in a wiki.
*
* @since 5.3M2
* @version $Id: 569b56ae5060434bb59350ee1c7d7d52d6eacd46 $
*/
@Component
@Named("wiki.user")
@Singleton
public class WikiUserManagerScriptService implements ScriptService
{
/**
* The key under which the last encountered error is stored in the current execution context.
*/
private static final String WIKIUSERERROR_KEY = "scriptservice.wiki.user.error";
@Inject
private WikiUserManager wikiUserManager;
@Inject
private WikiDescriptorManager wikiDescriptorManager;
@Inject
private AuthorizationManager authorizationManager;
@Inject
private Provider<XWikiContext> xcontextProvider;
@Inject
private DocumentReferenceResolver<String> documentReferenceResolver;
/**
* Provides access to the current context.
*/
@Inject
private Execution execution;
/**
* Get the error generated while performing the previously called action.
*
* @return an eventual exception or {@code null} if no exception was thrown
*/
public Exception getLastError()
{
return (Exception) this.execution.getContext().getProperty(WIKIUSERERROR_KEY);
}
/**
* Store a caught exception in the context, so that it can be later retrieved using {@link #getLastError()}.
*
* @param e the exception to store, can be {@code null} to clear the previously stored exception
* @see #getLastError()
*/
private void setLastError(Exception e)
{
this.execution.getContext().setProperty(WIKIUSERERROR_KEY, e);
}
/**
* @return the user scope
*/
public UserScope getUserScope()
{
return getUserScope(wikiDescriptorManager.getCurrentWikiId());
}
/**
* @param wikiId Id of the wiki to test
* @return the user scope
*/
public UserScope getUserScope(String wikiId)
{
try {
return wikiUserManager.getUserScope(wikiId);
} catch (WikiUserManagerException e) {
setLastError(e);
return null;
}
}
/**
* Check that all required permissions are respected by both the script and the user.
*
* @param wikiId the id of the wiki concerned by the operation
*
* @throws AccessDeniedException if the permissions are not respected
*/
private void checkRights(String wikiId) throws AccessDeniedException
{
checkRights(wikiId, null);
}
/**
* Check that all required permissions are respected by both the script and the user.
*
* @param wikiId the id of the wiki concerned by the operation
* @param user the user concerned by the operation
*
* @throws AccessDeniedException if the permissions are not respected
*/
private void checkRights(String wikiId, DocumentReference user) throws AccessDeniedException
{
XWikiContext context = xcontextProvider.get();
// Does the script author have the admin right?
//
// The goal is to avoid that a non-granted user writes a script, which could be executed by an administrator,
// which uses this script service to perform "nasty" operations, like being invited to a sub-wiki.
//
// By the past, we checked for the programing right, but it was too restrictive, as it make impossible to
// a user without programing rights to create a wiki and then invite some peoples in it.
authorizationManager.checkAccess(Right.ADMIN, context.getDoc().getAuthorReference(),
context.getDoc().getDocumentReference());
// Is the user concerned by the operation?
if (user != null && user.equals(context.getUserReference())) {
// If the user is concerned, then she has the right to perform this operation.
return;
}
// Does the current user have the admin right?
authorizationManager.checkAccess(Right.ADMIN, context.getUserReference(), new WikiReference(wikiId));
}
/**
* Check that all required permissions are respected by both the script and the user concerned by a candidacy.
*
* @param candidacy the candidacy concerned by the operation
*
* @throws AccessDeniedException if the permissions are not respected
*/
private void checkRights(MemberCandidacy candidacy) throws AccessDeniedException
{
checkRights(candidacy.getWikiId(), documentReferenceResolver.resolve(candidacy.getUserId()));
}
/**
* @param wikiId Id of the wiki to change
* @param scope scope to set
* @return true if it succeed
*/
public boolean setUserScope(String wikiId, String scope)
{
try {
checkRights(wikiId);
wikiUserManager.setUserScope(wikiId, UserScope.valueOf(scope.toUpperCase()));
} catch (WikiUserManagerException | AccessDeniedException | IllegalArgumentException e) {
setLastError(e);
return false;
}
return true;
}
/**
* @return the membership type of the current wiki
*/
public MembershipType getMembershipType()
{
return getMembershipType(wikiDescriptorManager.getCurrentWikiId());
}
/**
* @param wikiId Id of the wiki to test
* @return the membership type of the specified wiki
*/
public MembershipType getMembershipType(String wikiId)
{
try {
return wikiUserManager.getMembershipType(wikiId);
} catch (WikiUserManagerException e) {
setLastError(e);
return null;
}
}
/**
* @param wikiId Id of the wiki to change
* @param type the membership type to set
* @return true if it succeed
*/
public boolean setMembershipType(String wikiId, String type)
{
try {
checkRights(wikiId);
wikiUserManager.setMembershipType(wikiId, MembershipType.valueOf(type.toUpperCase()));
} catch (WikiUserManagerException | AccessDeniedException | IllegalArgumentException e) {
setLastError(e);
return false;
}
return true;
}
/**
* @param wikiId if the the wiki
* @return the list of all the members (global users) or null if something failed.
*/
public Collection<String> getMembers(String wikiId)
{
try {
return wikiUserManager.getMembers(wikiId);
} catch (WikiUserManagerException e) {
setLastError(e);
return null;
}
}
/**
* To know if a user is a member of a wiki.
*
* @param userId Id of the user
* @param wikiId Id of the wiki
* @return if the user is a member of the specified wiki or null if some problems occur
*/
public Boolean isMember(String userId, String wikiId)
{
try {
return wikiUserManager.isMember(userId, wikiId);
} catch (WikiUserManagerException e) {
setLastError(e);
return null;
}
}
/**
* Add a user as a member.
*
* @param userId UserID to add
* @param wikiId Id of the wiki
* @return true if it succeed
*/
public boolean addMember(String userId, String wikiId)
{
try {
checkRights(wikiId);
wikiUserManager.addMember(userId, wikiId);
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
return false;
}
return true;
}
/**
* Add a list of users as a members.
*
* @param userIds List of userID to add
* @param wikiId Id of the wiki
* @return true if it succeed
*/
public boolean addMembers(Collection<String> userIds, String wikiId)
{
try {
checkRights(wikiId);
wikiUserManager.addMembers(userIds, wikiId);
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
return false;
}
return true;
}
/**
* Remove a member.
*
* @param userId UserID to remove
* @param wikiId Id the the wiki
* @return true if it succeed
*/
public boolean removeMember(String userId, String wikiId)
{
try {
checkRights(wikiId);
wikiUserManager.removeMember(userId, wikiId);
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
return false;
}
return true;
}
private boolean canSeeCandidacy(MemberCandidacy candidacy)
{
XWikiContext context = xcontextProvider.get();
// Test if the user is concerned by the candidacy...
DocumentReference candidacyUser = documentReferenceResolver.resolve(candidacy.getUserId());
if (context.getUserReference().equals(candidacyUser)) {
// Hide the admin private comment
candidacy.setAdminPrivateComment(null);
return true;
}
// Otherwise the user must be an admin.
return authorizationManager.hasAccess(Right.ADMIN, context.getUserReference(),
new WikiReference(candidacy.getWikiId()));
}
/**
* Get the specified candidacy.
*
* @param wikiId Id of the wiki concerned by the candidacy
* @param candidacyId id of the candidacy
* @return the candidacy or null if problems occur
*/
public MemberCandidacy getCandidacy(String wikiId, int candidacyId)
{
// Get the candidacy
MemberCandidacy candidacy = null;
try {
candidacy = wikiUserManager.getCandidacy(wikiId, candidacyId);
// Check the rights
if (!canSeeCandidacy(candidacy)) {
setLastError(new WikiUserManagerScriptServiceException("You are not allowed to see this candidacy."));
candidacy = null;
}
} catch (WikiUserManagerException e) {
setLastError(e);
}
return candidacy;
}
/**
* Filter from a list of candidacies those that the current user has the right to see.
*
* @param candidacies list to filter
* @return the filtered list
*/
private Collection<MemberCandidacy> filterAuthorizedCandidacies(Collection<MemberCandidacy> candidacies)
{
Collection<MemberCandidacy> authorizedCandidacies = new ArrayList<MemberCandidacy>();
for (MemberCandidacy candidacy : candidacies) {
if (canSeeCandidacy(candidacy)) {
authorizedCandidacies.add(candidacy);
}
}
return authorizedCandidacies;
}
/**
* Get all the invitations to join a wiki.
*
* @param wikiId id of the wiki to join
* @return a list of invitations to join this wiki or null if some problems occur
*/
public Collection<MemberCandidacy> getAllInvitations(String wikiId)
{
try {
Collection<MemberCandidacy> candidacies = wikiUserManager.getAllInvitations(wikiId);
return filterAuthorizedCandidacies(candidacies);
} catch (WikiUserManagerException e) {
setLastError(e);
return null;
}
}
/**
* @param user DocumentReference to the user to test
* @param wikiId id of the wiki to test
* @return either or not the user has a pending invitation to join the wiki, null if some problems occur
*/
public Boolean hasPendingInvitation(DocumentReference user, String wikiId)
{
// Guest users never have pending invitations!
if (user == null) {
return false;
}
try {
checkRights(wikiId, user);
return wikiUserManager.hasPendingInvitation(user, wikiId);
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
}
return null;
}
/**
* @param user DocumentReference to the user to test
* @param wikiId id of the wiki to test
* @return either or not the user has a pending request to join the wiki, null if some problems occur
*/
public Boolean hasPendingRequest(DocumentReference user, String wikiId)
{
// Guest users never have pending requests!
if (user == null) {
return false;
}
try {
checkRights(wikiId, user);
return wikiUserManager.hasPendingRequest(user, wikiId);
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
}
return null;
}
/**
* Get all the join requests for a wiki that the current user has the right to see.
*
* @param wikiId id of the wiki to join
* @return a list of join request for this wiki or null if some problems occur
*/
public Collection<MemberCandidacy> getAllRequests(String wikiId)
{
try {
Collection<MemberCandidacy> candidacies = wikiUserManager.getAllRequests(wikiId);
return filterAuthorizedCandidacies(candidacies);
} catch (WikiUserManagerException e) {
setLastError(e);
return null;
}
}
/**
* Join a wiki.
*
* @param userId userId to add to the wiki
* @param wikiId id of the wiki
* @return true if it succeed
*/
public boolean join(String userId, String wikiId)
{
// Check if the current user is userId
XWikiContext context = xcontextProvider.get();
DocumentReference candidacyUser = documentReferenceResolver.resolve(userId);
if (!context.getUserReference().equals(candidacyUser)) {
setLastError(new WikiUserManagerException(String.format("User [%s] cannot call "
+ "$services.wiki.user.join() with an other userId.", context.getUserReference())));
return false;
}
try {
wikiUserManager.join(userId, wikiId);
} catch (WikiUserManagerException e) {
setLastError(e);
return false;
}
return true;
}
/**
* Leave a wiki.
*
* @param userId userId to remove from the wiki
* @param wikiId id of the wiki
* @return true if it succeed
*/
public boolean leave(String userId, String wikiId)
{
// Check if the current user is userId
XWikiContext context = xcontextProvider.get();
DocumentReference candidacyUser = documentReferenceResolver.resolve(userId);
if (!context.getUserReference().equals(candidacyUser)) {
setLastError(new WikiUserManagerException(String.format("User [%s] cannot call $services.wiki.user.leave()"
+ " with an other userId.", context.getUserReference())));
return false;
}
// Leave the wiki
try {
wikiUserManager.leave(userId, wikiId);
} catch (WikiUserManagerException e) {
setLastError(e);
return false;
}
return true;
}
/**
* Perform a request to join a wiki.
*
* @param userId UserID of the requester
* @param wikiId Id of the wiki to join
* @param message Message that motivates the request
* @return the generated candidacy
*/
public MemberCandidacy askToJoin(String userId, String wikiId, String message)
{
try {
checkRights(wikiId, documentReferenceResolver.resolve(userId));
return wikiUserManager.askToJoin(userId, wikiId, message);
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
return null;
}
}
/**
* Accept the request to join the wiki.
*
* @param request request to accept
* @param message message about the acceptance
* @param privateComment private comment that only the administrator can see
* @return true if it succeed
*/
public boolean acceptRequest(MemberCandidacy request, String message, String privateComment)
{
try {
checkRights(request);
wikiUserManager.acceptRequest(request, message, privateComment);
return true;
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
}
return false;
}
/**
* Refuse the request to join the wiki.
*
* @param request request to refuse
* @param message message about the refusal
* @param privateComment private comment that only the administrator can see
* @return true if it succeed
*/
public boolean refuseRequest(MemberCandidacy request, String message, String privateComment)
{
try {
checkRights(request);
wikiUserManager.refuseRequest(request, message, privateComment);
return true;
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
}
return false;
}
/**
* Cancel a candidacy.
*
* @param candidacy Candidacy to cancel
* @return true if it succeed
*/
public boolean cancelCandidacy(MemberCandidacy candidacy)
{
try {
checkRights(candidacy);
wikiUserManager.cancelCandidacy(candidacy);
return true;
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
}
return false;
}
/**
* Invite a global user to a wiki.
*
* @param userId Id of the user to add
* @param wikiId Id of the wiki to join
* @param message MemberCandidacy message
* @return The generated invitation or null if problems occur
*/
public MemberCandidacy invite(String userId, String wikiId, String message)
{
try {
checkRights(wikiId);
return wikiUserManager.invite(userId, wikiId, message);
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
}
return null;
}
/**
* Accept the invitation to join a wiki.
*
* @param invitation invitation to accept
* @param message message that goes along the acceptance
* @return true if it succeed
*/
public boolean acceptInvitation(MemberCandidacy invitation, String message)
{
try {
checkRights(invitation);
wikiUserManager.acceptInvitation(invitation, message);
return true;
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
}
return false;
}
/**
* Refuse the invitation to join a wiki.
*
* @param invitation invitation to refuse
* @param message message that goes along the refusal
* @return true if it succeed
*/
public boolean refuseInvitation(MemberCandidacy invitation, String message)
{
try {
checkRights(invitation);
wikiUserManager.refuseInvitation(invitation, message);
return true;
} catch (AccessDeniedException | WikiUserManagerException e) {
setLastError(e);
}
return false;
}
}