Dave Ward e31ea91e51 Merged V4.0-BUG-FIX to HEAD
35224: ALF-12038: Remove trailing JSON comma causing IE7 script error
   35226: ALF-13401 - Saving PowerPoint (mac 2011) via CIFS fails in Mac OS X Lion
   35239: ALF-13409: Further fix to deal with concurrent deletion of a user's site invitations in background
   35245: ALF-13281: Enabled use of autocomplete in IE for forms runtime. This change also allows multiple events to be attached per validation handler
   35253: ALF-13640: Fixed issues with updating task associations + added new test + fixed existing activiti-component-tests
   35271: Translation updates (fixes: ALF-13434) - based on EN r35212. (Dutch still to follow)
   35281: ALF-13227: Fix CSS for Wiki layout of nested lists
   35284: SPANISH: Update from Gloria
   35290: More debug + unit test for mac powerpoint shuffle.
   35291: Added isTemporary method
   35295: ALF-13453	: Remote Code Execution (can create reverse shell).
   - Added ability for XMLUtil parse callers to provide an  optional array of XMLFilterImpl to be used while parsing. 
   -Added secureParseXSL methods that automatically install an XMLFilterImpl that causes a parse failure if any insecure namespaces are encountered.
   35303: Fix for ALF-12444 Node Browser improvement: Index single node and remove single node from indexes
   Part of ALF-13723 SOLR does not include the same query unit tests as lucene
   35305: ALF-13723 SOLR does not include the same query unit tests as lucene
   - test template
   35306: ALF-13723 SOLR does not include the same query unit tests as lucene
   - template for creating test cores
   35323: ALF-13420: Natural sort on form option labels and improvement for CSS - specifically to address transform action in document details.
   35328: ALF-13409: Avoid concurrency issues in unit test tear downs by deleting users before sites. User deletion deletes invitations synchronously. Site deletion deletes invitations concurrently to avoid UI timeouts. The potential to access invitations that are being concurrently deleted still exists, but always did!
   35331: ALF-12126: Ensure that DND upload is disabled for users with only consumer access
   35335: ALF-13708: Merged V3.4-BUG-FIX (3.4.10) to V4.0-BUG-FIX (4.0.2)
      35235: ALF-13673: Amp-loaded duplicated mimetypes should be handled
         - Modified code to allow duplicates to replace parts of the existing mimetype definitions.
         - A warning is logged each time. 
   35336: Spanish and Dutch updates from Gloria, based on EN r35212
   35355: Merged V3.4-BUG-FIX to V4.0-BUG-FIX
      35213: ALF-13686: Merged PATCHES/V3.4.8 to V3.4-BUG-FIX
         34943: ALF-13121: Option to create users either as user1 or user1@domain.com after kerberos authentication
         - New Kerberos subsystem parameter kerberos.authentication.stripUsernameSuffix introduced
         - When true (the default) the @domain sufix will be stripped from Kerberos authenticated usernames in CIFS, SPP, WebDAV and the Web Client
         - When false, should enable a multi-domain customer to use Alfresco (says Mr Gninot)
         35096: ALF-13121: Added missing stripKerberosUsernameSuffix property to sharepointAuthenticationHandler
      35215: ALF-13065: Ensure Wiki new page save button is available on HTML edit action
      35219: ALF-11898: Fixed TinyMCE create HTML content problem for Explorer client
      35261: Translation updates based on EN r35144
      35339: AD 2008 R2, user import via LDAP fails with over 1000 users
      - Problem discovered by Community user with simple workaround
      https://forums.alfresco.com/en/viewtopic.php?f=57&t=43960&sid=5569e5cfbccb3776e11ef4a8e9d50378&p=129664#p129664
      35353: Merged V3.4 to V3.4-BUG-FIX
         35279: ALF-13713: Merged PATCHES/V3.4.8 to V3.4
            35146: Merged DEV to PATCHES/V3.4.8
               35130: ALF-13472: Webdav Does not allow a user to access spaces without read permission on parent spaces
                  Receiving of indirect lock is wrapped into AuthenticationUtil.runAs() invocation to provide a possibility of getting indirect lock for users with appropriate access rights for requested resource
         35280: ALF-10353: Internet Explorer hangs when using the object picker with a larger number of documents
         - reviewed by DD
         35318: ALF-13715: Merged HEAD to V3.4
            31743: Fixed ALF-10157: Web Form Details page for the "Selected Web Content Forms": script error appears on help button click: container.jsp (line 382)
         35341: ALF-13552: Merged V4.0 to V3.4
            35296: ALF-13453: Remote Code Execution (can create reverse shell) - Fix by Shane
            35304: ALF-13453: Extra fix to ensure xalan namespace isn't declared with global scope and can't be hijacked by an input stylesheet
            35307: ALF-13453: Duplicated extra fix to duplicate code in XSLTRenderingEngine!
      35354: Merged V3.4 to V3.4-BUG-FIX (RECORD ONLY)
         35266: Merged V3.4-BUG-FIX to V3.4
            35261: Translation updates based on EN r35144
         35334: Merged V3.4-BUG-FIX to V3.4
            35235: ALF-13673: Amp-loaded duplicated mimetypes should be handled
               - Modified code to allow duplicates to replace parts of the existing mimetype definitions.
               - A warning is logged each time. 
   35356: Merged V4.0 to V4.0-BUG-FIX
      35292: ALF-13721: Merged PATCHES/V4.0.0 to V4.0
         35240: Fix for ALF-13685 The SOLr textContent webscript is not protected by authentication and permission checks.
         35242: Fix for ALF-13685 The SOLr textContent webscript is not protected by authentication and permission checks.
         - /wcs/api/solr and /wcservice/api/solr
      35304: ALF-13453: Extra fix to ensure xalan namespace isn't declared with global scope and can't be hijacked by an input stylesheet
      35307: ALF-13453: Duplicated extra fix to duplicate code in XSLTRenderingEngine!
   35357: Merged V4.0 to V4.0-BUG-FIX (RECORD ONLY)
      35048: Merged V4.0-BUG-FIX to V4.0
         35031: Fix for ALF-12309: Script errors on site pages
      35293: Merged V4.0-BUG-FIX to V4.0
         35172: ALF-13626: category.put.json.ftl has wrong bracket
      35296: Merged V4.0-BUG-FIX to V4.0
         35295: ALF-13453: Remote Code Execution (can create reverse shell)
           - Fix by Shane


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35359 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2012-04-18 09:42:39 +00:00

1562 lines
64 KiB
Java

/*
* Copyright (C) 2005-2011 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco 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 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.invitation;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.admin.SysAdminParams;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.PasswordGenerator;
import org.alfresco.repo.security.authentication.UserNameGenerator;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.repo.workflow.CancelWorkflowActionExecuter;
import org.alfresco.repo.workflow.WorkflowModel;
import org.alfresco.repo.workflow.activiti.ActivitiConstants;
import org.alfresco.repo.workflow.jbpm.JBPMEngine;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.invitation.Invitation;
import org.alfresco.service.cmr.invitation.InvitationException;
import org.alfresco.service.cmr.invitation.InvitationExceptionForbidden;
import org.alfresco.service.cmr.invitation.InvitationExceptionNotFound;
import org.alfresco.service.cmr.invitation.InvitationExceptionUserError;
import org.alfresco.service.cmr.invitation.InvitationSearchCriteria;
import org.alfresco.service.cmr.invitation.InvitationService;
import org.alfresco.service.cmr.invitation.ModeratedInvitation;
import org.alfresco.service.cmr.invitation.NominatedInvitation;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.workflow.WorkflowAdminService;
import org.alfresco.service.cmr.workflow.WorkflowDefinition;
import org.alfresco.service.cmr.workflow.WorkflowException;
import org.alfresco.service.cmr.workflow.WorkflowPath;
import org.alfresco.service.cmr.workflow.WorkflowService;
import org.alfresco.service.cmr.workflow.WorkflowTask;
import org.alfresco.service.cmr.workflow.WorkflowTaskQuery;
import org.alfresco.service.cmr.workflow.WorkflowTaskState;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.UrlUtil;
import org.alfresco.util.collections.CollectionUtils;
import org.alfresco.util.collections.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.surf.util.I18NUtil;
/**
* Implementation of invitation service.
*
* @see org.alfresco.service.cmr.invitation.Invitation
* @author mrogers
* @author Nick Smith
*/
public class InvitationServiceImpl implements InvitationService, NodeServicePolicies.BeforeDeleteNodePolicy
{
private static final Log logger = LogFactory.getLog(InvitationServiceImpl.class);
/**
* Services
*/
private WorkflowService workflowService;
private WorkflowAdminService workflowAdminService;
private ActionService actionService;
private PersonService personService;
private SiteService siteService;
private MutableAuthenticationService authenticationService;
private PermissionService permissionService;
private DictionaryService dictionaryService;
private NamespaceService namespaceService;
private NodeService nodeService;
// user name and password generation beans
private UserNameGenerator usernameGenerator;
private PasswordGenerator passwordGenerator;
private PolicyComponent policyComponent;
private SysAdminParams sysAdminParams;
// maximum number of tries to generate a invitee user name which
// does not already belong to an existing person
public static final int MAX_NUM_INVITEE_USER_NAME_GEN_TRIES = 10;
private int maxUserNameGenRetries = MAX_NUM_INVITEE_USER_NAME_GEN_TRIES;
// Property determining whether emails should be sent.
private boolean sendEmails = true;
/**
* Set the policy component
*
* @param policyComponent policy component
*/
public void setPolicyComponent(PolicyComponent policyComponent)
{
this.policyComponent = policyComponent;
}
/**
* Checks that all necessary properties and services have been provided.
*/
public void init()
{
PropertyCheck.mandatory(this, "nodeService", nodeService);
PropertyCheck.mandatory(this, "WorkflowService", workflowService);
PropertyCheck.mandatory(this, "ActionService", actionService);
PropertyCheck.mandatory(this, "PersonService", personService);
PropertyCheck.mandatory(this, "SiteService", siteService);
PropertyCheck.mandatory(this, "AuthenticationService", authenticationService);
PropertyCheck.mandatory(this, "PermissionService", permissionService);
PropertyCheck.mandatory(this, "NamespaceService", namespaceService);
PropertyCheck.mandatory(this, "NodeService", nodeService);
PropertyCheck.mandatory(this, "UserNameGenerator", usernameGenerator);
PropertyCheck.mandatory(this, "PasswordGenerator", passwordGenerator);
PropertyCheck.mandatory(this, "PolicyComponent", policyComponent);
//
this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
SiteModel.TYPE_SITE, new JavaBehaviour(this, "beforeDeleteNode"));
this.policyComponent.bindClassBehaviour(BeforeDeleteNodePolicy.QNAME, ContentModel.TYPE_PERSON, new JavaBehaviour(this, "beforeDeleteNode"));
}
/**
* Get the names of the workflows which are managed by the invitation
* service
*
* @return the workflows which are managed by the invitation service
*/
public List<String> getInvitationServiceWorkflowNames()
{
List<String> ret = new ArrayList<String>(3);
ret.add(WorkflowModelNominatedInvitation.WORKFLOW_DEFINITION_NAME);
ret.add(WorkflowModelNominatedInvitation.WORKFLOW_DEFINITION_NAME_ACTIVITI);
ret.add(WorkflowModelModeratedInvitation.WORKFLOW_DEFINITION_NAME);
ret.add(WorkflowModelModeratedInvitation.WORKFLOW_DEFINITION_NAME_ACTIVITI);
// old deprecated invitation workflow.
ret.add("jbpm$wf:invite");
return ret;
}
/**
* Start the invitation process for a NominatedInvitation
*
* @param inviteeUserName Alfresco user name of the invitee
* @param Invitation
* @param ResourceType resourceType
* @param resourceName
* @param inviteeRole
* @param serverPath
* @param acceptUrl
* @param rejectUrl
* @return the nominated invitation which will contain the invitationId and
* ticket which will uniqely identify this invitation for the rest
* of the workflow.
* @throws InvitationException
* @throws InvitationExceptionUserError
* @throws InvitationExceptionForbidden
*/
public NominatedInvitation inviteNominated(String inviteeUserName, Invitation.ResourceType resourceType,
String resourceName, String inviteeRole, String serverPath, String acceptUrl, String rejectUrl)
{
// inviteeUserName was specified
NodeRef person = this.personService.getPerson(inviteeUserName);
Serializable firstNameVal = this.getNodeService().getProperty(person, ContentModel.PROP_FIRSTNAME);
Serializable lastNameVal = this.getNodeService().getProperty(person, ContentModel.PROP_LASTNAME);
Serializable emailVal = this.getNodeService().getProperty(person, ContentModel.PROP_EMAIL);
String firstName = DefaultTypeConverter.INSTANCE.convert(String.class, firstNameVal);
String lastName = DefaultTypeConverter.INSTANCE.convert(String.class, lastNameVal);
String email = DefaultTypeConverter.INSTANCE.convert(String.class, emailVal);
return inviteNominated(firstName, lastName, email, inviteeUserName, resourceType, resourceName, inviteeRole,
serverPath, acceptUrl, rejectUrl);
}
/**
* Start the invitation process for a NominatedInvitation
*
* @param inviteeUserName Alfresco user name of the invitee
* @param Invitation
* @param ResourceType resourceType
* @param resourceName
* @param inviteeRole
* @param acceptUrl
* @param rejectUrl
* @return the nominated invitation which will contain the invitationId and
* ticket which will uniqely identify this invitation for the rest
* of the workflow.
* @throws InvitationException
* @throws InvitationExceptionUserError
* @throws InvitationExceptionForbidden
*/
public NominatedInvitation inviteNominated(String inviteeUserName, Invitation.ResourceType resourceType,
String resourceName, String inviteeRole, String acceptUrl, String rejectUrl)
{
// inviteeUserName was specified
NodeRef person = this.personService.getPerson(inviteeUserName);
Serializable firstNameVal = this.getNodeService().getProperty(person, ContentModel.PROP_FIRSTNAME);
Serializable lastNameVal = this.getNodeService().getProperty(person, ContentModel.PROP_LASTNAME);
Serializable emailVal = this.getNodeService().getProperty(person, ContentModel.PROP_EMAIL);
String firstName = DefaultTypeConverter.INSTANCE.convert(String.class, firstNameVal);
String lastName = DefaultTypeConverter.INSTANCE.convert(String.class, lastNameVal);
String email = DefaultTypeConverter.INSTANCE.convert(String.class, emailVal);
String serverPath = UrlUtil.getShareUrl(sysAdminParams);
return inviteNominated(firstName, lastName, email, inviteeUserName, resourceType, resourceName, inviteeRole,
serverPath, acceptUrl, rejectUrl);
}
/**
* Start the invitation process for a NominatedInvitation
*
* @param inviteeFirstName
* @param inviteeLastName
* @param inviteeEmail
* @param inviteeUserName optional Alfresco user name of the invitee, null
* if not on system.
* @param Invitation .ResourceType resourceType
* @param resourceName
* @param inviteeRole
* @param acceptUrl
* @param rejectUrl
* @return the nominated invitation which will contain the invitationId and
* ticket which will uniqely identify this invitation for the rest
* of the workflow.
* @throws InvitationException
* @throws InvitationExceptionUserError
* @throws InvitationExceptionForbidden
*/
public NominatedInvitation inviteNominated(String inviteeFirstName, String inviteeLastName, String inviteeEmail,
Invitation.ResourceType resourceType, String resourceName, String inviteeRole, String acceptUrl, String rejectUrl)
{
String serverPath = UrlUtil.getShareUrl(sysAdminParams);
return inviteNominated(inviteeFirstName, inviteeLastName, inviteeEmail, null, resourceType, resourceName,
inviteeRole, serverPath, acceptUrl, rejectUrl);
}
/**
* Start the invitation process for a NominatedInvitation
*
* @param inviteeFirstName
* @param inviteeLastName
* @param inviteeEmail
* @param inviteeUserName optional Alfresco user name of the invitee, null
* if not on system.
* @param Invitation .ResourceType resourceType
* @param resourceName
* @param inviteeRole
* @param serverPath
* @param acceptUrl
* @param rejectUrl
* @return the nominated invitation which will contain the invitationId and
* ticket which will uniqely identify this invitation for the rest
* of the workflow.
* @throws InvitationException
* @throws InvitationExceptionUserError
* @throws InvitationExceptionForbidden
*/
public NominatedInvitation inviteNominated(String inviteeFirstName, String inviteeLastName, String inviteeEmail,
Invitation.ResourceType resourceType, String resourceName, String inviteeRole, String serverPath,
String acceptUrl, String rejectUrl)
{
return inviteNominated(inviteeFirstName, inviteeLastName, inviteeEmail, null, resourceType, resourceName,
inviteeRole, serverPath, acceptUrl, rejectUrl);
}
// Temporary method
private NominatedInvitation inviteNominated(String inviteeFirstName, String inviteeLastName, String inviteeEmail,
String inviteeUserName, Invitation.ResourceType resourceType, String resourceName, String inviteeRole,
String serverPath, String acceptUrl, String rejectUrl)
{
// Validate the request
// Check resource exists
if (resourceType == Invitation.ResourceType.WEB_SITE)
{
return startNominatedInvite(inviteeFirstName, inviteeLastName, inviteeEmail, inviteeUserName, resourceType,
resourceName, inviteeRole, serverPath, acceptUrl, rejectUrl);
}
throw new InvitationException("unknown resource type");
}
/**
* Start the invitation process for a ModeratedInvitation
*
* @param comments why does the invitee want access to the resource ?
* @param inviteeUserName who is to be invited
* @param Invitation .ResourceType resourceType what resource type ?
* @param resourceName which resource
* @param inviteeRole which role ?
*/
public ModeratedInvitation inviteModerated(String inviteeComments, String inviteeUserName,
Invitation.ResourceType resourceType, String resourceName, String inviteeRole)
{
if (resourceType == Invitation.ResourceType.WEB_SITE)
{
return startModeratedInvite(inviteeComments, inviteeUserName, resourceType, resourceName, inviteeRole);
}
throw new InvitationException("unknown resource type");
}
/**
* Invitee accepts this invitation Nominated Invitaton process only
*
* @param invitationId the invitation id
* @param ticket the ticket produced when creating the invitation.
*/
public Invitation accept(String invitationId, String ticket)
{
WorkflowTask startTask = getStartTask(invitationId);
NominatedInvitation invitation = getNominatedInvitation(startTask);
if(invitation == null)
{
throw new InvitationException("State error, accept may only be called on a nominated invitation.");
}
// Check invitationId and ticket match
if(invitation.getTicket().equals(ticket)==false)
{
//TODO localise msg
String msg = "Response to invite has supplied an invalid ticket. The response to the invitation could thus not be processed";
throw new InvitationException(msg);
}
endInvitation(startTask,
WorkflowModelNominatedInvitation.WF_TRANSITION_ACCEPT, null,
WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING, WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING);
return invitation;
}
private void endInvitation(WorkflowTask startTask, String transition, Map<QName, Serializable> properties, QName... taskTypes )
{
// Deleting a person can cancel their invitations. Cancelling invitations can delete inactive persons! So prevent infinite looping here
if (TransactionalResourceHelper.getSet(getClass().getName()).add(startTask.getPath().getInstance().getId()))
{
List<WorkflowTask> tasks = workflowService.getTasksForWorkflowPath(startTask.getPath().getId());
if(tasks.size()==1)
{
WorkflowTask task = tasks.get(0);
if(taskTypeMatches(task, taskTypes))
{
if(properties != null)
{
workflowService.updateTask(task.getId(), properties, null, null);
}
workflowService.endTask(task.getId(), transition);
return;
}
}
// Throw exception if the task not found.
Object objs[] = { startTask.getPath().getInstance().getId() };
throw new InvitationExceptionUserError("invitation.invite.already_finished", objs);
}
}
/**
* Moderator approves this invitation
*
* @param request the request to approve
* @param reason comments about the acceptance
*/
public Invitation approve(String invitationId, String reason)
{
WorkflowTask startTask = getStartTask(invitationId);
ModeratedInvitation invitation = getModeratedInvitation(startTask);
if(invitation == null)
{
String msg = "State error, can only call approve on a Moderated invitation.";
throw new InvitationException(msg);
}
// Check approver is a site manager
String currentUser = this.authenticationService.getCurrentUserName();
checkManagerRole(currentUser, invitation.getResourceType(), invitation.getResourceName());
Map<QName, Serializable> wfReviewProps = new HashMap<QName, Serializable>();
wfReviewProps.put(ContentModel.PROP_OWNER, currentUser);
wfReviewProps.put(WorkflowModelModeratedInvitation.WF_PROP_REVIEW_COMMENTS, reason);
endInvitation(startTask,
WorkflowModelModeratedInvitation.WF_TRANSITION_APPROVE,
wfReviewProps,
WorkflowModelModeratedInvitation.WF_ACTIVITI_REVIEW_TASK, WorkflowModelModeratedInvitation.WF_REVIEW_TASK);
return invitation;
}
/**
* User or moderator rejects this request
*
* @param invitationId
* @param reason , optional reason for rejection
*/
public Invitation reject(String invitationId, String reason)
{
WorkflowTask startTask = getStartTask(invitationId);
if(taskTypeMatches(startTask, WorkflowModelModeratedInvitation.WF_START_TASK))
{
return rejectModeratedInvitation(startTask, reason);
}
else
{
return rejectNominatedInvitation(startTask);
}
}
private Invitation rejectModeratedInvitation(WorkflowTask startTask, String reason)
{
ModeratedInvitation invitation = getModeratedInvitation(startTask);
// Check rejecter is a site manager and throw and exception if not
String rejecterUserName = this.authenticationService.getCurrentUserName();
checkManagerRole(rejecterUserName, invitation.getResourceType(), invitation.getResourceName());
Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
properties.put(ContentModel.PROP_OWNER, rejecterUserName);
properties.put(WorkflowModelModeratedInvitation.WF_PROP_REVIEW_COMMENTS, reason);
endInvitation(startTask,
WorkflowModelModeratedInvitation.WF_TRANSITION_REJECT,
properties,
WorkflowModelModeratedInvitation.WF_ACTIVITI_REVIEW_TASK, WorkflowModelModeratedInvitation.WF_REVIEW_TASK);
return invitation;
}
private Invitation rejectNominatedInvitation(WorkflowTask startTask)
{
NominatedInvitation invitation = getNominatedInvitation(startTask);
endInvitation(startTask,
WorkflowModelNominatedInvitation.WF_TRANSITION_REJECT, null,
WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING, WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING);
return invitation;
}
/**
* {@inheritDoc}
*/
public Invitation cancel(String invitationId)
{
try
{
WorkflowTask startTask = getStartTask(invitationId);
if (taskTypeMatches(startTask, WorkflowModelModeratedInvitation.WF_START_TASK))
{
return cancelModeratedInvitation(startTask);
}
else
{
return cancelNominatedInvitation(startTask);
}
}
catch (InvitationExceptionNotFound e)
{
// Invitation already deleted or deleted in background
return null;
}
}
private Invitation cancelModeratedInvitation(WorkflowTask startTask)
{
ModeratedInvitation invitation = getModeratedInvitation(startTask);
String currentUserName = this.authenticationService.getCurrentUserName();
if (!AuthenticationUtil.isRunAsUserTheSystemUser())
{
if (false == currentUserName.equals(invitation.getInviteeUserName()))
{
checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName());
}
}
// Only proceed with the cancel if the site still exists (the site may have been deleted and invitations may be
// getting cancelled in the background)
if (this.siteService.getSite(invitation.getResourceName()) != null)
{
workflowService.cancelWorkflow(invitation.getInviteId());
}
return invitation;
}
private Invitation cancelNominatedInvitation(WorkflowTask startTask)
{
NominatedInvitation invitation = getNominatedInvitation(startTask);
String currentUserName = this.authenticationService.getCurrentUserName();
if (!AuthenticationUtil.isRunAsUserTheSystemUser())
{
if (false == currentUserName.equals(invitation.getInviterUserName()))
{
checkManagerRole(currentUserName, invitation.getResourceType(), invitation.getResourceName());
}
}
// Only proceed with the cancel if the site still exists (the site may have been deleted and invitations may be
// getting cancelled in the background)
if (this.siteService.getSite(invitation.getResourceName()) != null)
{
endInvitation(startTask, WorkflowModelNominatedInvitation.WF_TRANSITION_CANCEL, null,
WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING,
WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING);
}
return invitation;
}
/**
* Get an invitation from its invitation id <BR />
* Invitations are returned which may be in progress or completed.
*
* @throws InvitationExceptionNotFound the invitation does not exist.
* @throws InvitationExceptionUserError
* @return the invitation.
*/
public Invitation getInvitation(String invitationId)
{
WorkflowTask startTask = getStartTask(invitationId);
return getInvitation(startTask);
}
private Invitation getInvitation(WorkflowTask startTask)
{
Invitation invitation = getNominatedInvitation(startTask);
if(invitation == null)
{
invitation = getModeratedInvitation(startTask);
}
return invitation;
}
private Map<String, WorkflowTask> getInvitationTasks(List<String> invitationIds)
{
for (String invitationId: invitationIds)
{
validateInvitationId(invitationId);
}
// query for invite workflow task associate
long start = (logger.isDebugEnabled()) ? System.currentTimeMillis() : 0;
List<WorkflowTask> inviteStartTasks = workflowService.getStartTasks(invitationIds, true);
if (logger.isDebugEnabled())
{
logger.debug(" getInvitationTask("+invitationIds.size()+") in "+ (System.currentTimeMillis()-start) + " ms");
}
Map<String, WorkflowTask> result = new HashMap<String, WorkflowTask>(inviteStartTasks.size() * 2);
for(WorkflowTask inviteStartTask: inviteStartTasks)
{
String invitationId = inviteStartTask.getPath().getInstance().getId();
// The following does not work for moderated tasks
// String invitationId = (String)
// inviteStartTask.getProperties().get(WorkflowModel.PROP_WORKFLOW_INSTANCE_ID);
result.put(invitationId, inviteStartTask);
}
return result;
}
private ModeratedInvitation getModeratedInvitation(WorkflowTask startTask)
{
ModeratedInvitation invitation = null;
if (taskTypeMatches(startTask, WorkflowModelModeratedInvitation.WF_START_TASK))
{
String invitationId = startTask.getPath().getInstance().getId();
invitation = new ModeratedInvitationImpl(invitationId, startTask.getProperties());
}
return invitation;
}
private NominatedInvitation getNominatedInvitation(WorkflowTask startTask)
{
NominatedInvitation invitation = null;
if (taskTypeMatches(startTask, WorkflowModelNominatedInvitation.WF_TASK_INVITE_TO_SITE))
{
Date inviteDate = startTask.getPath().getInstance().getStartDate();
String invitationId = startTask.getPath().getInstance().getId();
invitation = new NominatedInvitationImpl(invitationId, inviteDate, startTask.getProperties());
}
return invitation;
}
private boolean taskTypeMatches(WorkflowTask task, QName... types)
{
QName taskDefName = task.getDefinition().getMetadata().getName();
return Arrays.asList(types).contains(taskDefName);
}
private WorkflowTask getStartTask(String invitationId)
{
validateInvitationId(invitationId);
WorkflowTask startTask = null;
try
{
startTask = workflowService.getStartTask(invitationId);
}
catch (WorkflowException we)
{
// Do nothing
}
if (startTask == null)
{
Object objs[] = { invitationId };
throw new InvitationExceptionNotFound("invitation.error.not_found", objs);
}
return startTask;
}
/**
* list Invitations for a specific person/invitee
*
* @param invitee alfresco user id of person being invited
*/
public List<Invitation> listPendingInvitationsForInvitee(String invitee)
{
InvitationSearchCriteriaImpl crit = new InvitationSearchCriteriaImpl();
crit.setInvitationType(InvitationSearchCriteria.InvitationType.ALL);
crit.setInvitee(invitee);
return searchInvitation(crit);
}
/**
* list Invitations for a specific resource
*
* @param resourceType
* @param resourceName
*/
public List<Invitation> listPendingInvitationsForResource(Invitation.ResourceType resourceType, String resourceName)
{
InvitationSearchCriteriaImpl criteria = getPendingInvitationCriteriaForResource(resourceType, resourceName);
return searchInvitation(criteria);
}
/**
* Returns search criteria to find pending invitations
* @param resourceType
* @param resourceName
* @return search criteria
*/
private InvitationSearchCriteriaImpl getPendingInvitationCriteriaForResource(
Invitation.ResourceType resourceType, String resourceName)
{
InvitationSearchCriteriaImpl criteria = new InvitationSearchCriteriaImpl();
criteria.setInvitationType(InvitationSearchCriteria.InvitationType.ALL);
criteria.setResourceType(resourceType);
criteria.setResourceName(resourceName);
return criteria;
}
/**
* This is the general search invitation method returning {@link Invitation}s
*
* @param criteria
* @return the list of start tasks for invitations
*/
public List<Invitation> searchInvitation(final InvitationSearchCriteria criteria)
{
int limit = 200;
List<String> invitationIds = searchInvitationsForIds(criteria, limit);
return invitationIds.isEmpty() ? Collections.<Invitation>emptyList() : searchInvitation(criteria, invitationIds);
}
private List<Invitation> searchInvitation(final InvitationSearchCriteria criteria, List<String> invitationIds)
{
final Map<String, WorkflowTask> taskCache = getInvitationTasks(invitationIds);
return CollectionUtils.transform(invitationIds, new Function<String, Invitation>()
{
public Invitation apply(String invitationId)
{
WorkflowTask startTask = taskCache.get(invitationId);
if (startTask == null)
{
return null;
}
Invitation invitation = getInvitation(startTask);
return invitationMatches(invitation, criteria) ? invitation : null;
}
});
}
/**
* This is a general search invitation method returning IDs
*
* @param criteria
* @param limit maximum number of IDs to return. If less than 1, there is no limit.
* @return the list of invitation IDs (the IDs of the invitations not the IDs of the invitation start tasks)
*/
private List<String> searchInvitationsForIds(final InvitationSearchCriteria criteria, int limit)
{
List<String> invitationIds = new ArrayList<String>();
InvitationSearchCriteria.InvitationType toSearch = criteria.getInvitationType();
if (toSearch == InvitationSearchCriteria.InvitationType.ALL
|| toSearch == InvitationSearchCriteria.InvitationType.NOMINATED)
{
for (WorkflowTask task : searchNominatedInvitations(criteria))
{
String invitationId = task.getPath().getInstance().getId();
invitationIds.add(invitationId);
if (limit > 0 && invitationIds.size() >= limit)
{
break;
}
}
}
if ((limit <= 0 || invitationIds.size() < limit) &&
(toSearch == InvitationSearchCriteria.InvitationType.ALL
|| toSearch == InvitationSearchCriteria.InvitationType.MODERATED))
{
for (WorkflowTask task: searchModeratedInvitations(criteria))
{
String invitationId = task.getPath().getInstance().getId();
invitationIds.add(invitationId);
if (limit > 0 && invitationIds.size() >= limit)
{
break;
}
}
}
return invitationIds;
}
/**
* Fix for ALF-2598
* @param invitation
* @param criteria
* @return
*/
private boolean invitationMatches(Invitation invitation, InvitationSearchCriteria criteria)
{
String invitee = criteria.getInvitee();
if (invitee!= null && false == invitee.equals(invitation.getInviteeUserName()))
{
return false;
}
String inviter = criteria.getInviter();
if(inviter!= null)
{
if (invitation instanceof NominatedInvitation)
{
NominatedInvitation modInvite = (NominatedInvitation) invitation;
if(false == inviter.equals(modInvite.getInviterUserName()))
{
return false;
}
}
else
{
return false;
}
}
String resourceName= criteria.getResourceName();
if (resourceName!= null && false == resourceName.equals(invitation.getResourceName()))
{
return false;
}
return true;
}
private List<WorkflowTask> searchModeratedInvitations(InvitationSearchCriteria criteria)
{
long start = (logger.isDebugEnabled()) ? System.currentTimeMillis() : 0;
WorkflowTaskQuery query = new WorkflowTaskQuery();
query.setTaskState(WorkflowTaskState.IN_PROGRESS);
Map<QName, Object> properties = new HashMap<QName, Object>();
String invitee = criteria.getInvitee();
if (invitee != null)
{
properties.put(WorkflowModelModeratedInvitation.WF_PROP_INVITEE_USER_NAME, invitee);
}
//TODO Uncomment if more than one ResourceType added.
// ResourceType resourceType = criteria.getResourceType();
// if (resourceType != null)
// {
// properties.put(WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_TYPE, resourceType.toString());
// }
String resourceName = criteria.getResourceName();
if (resourceName != null)
{
properties.put(WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_NAME, resourceName);
}
query.setProcessCustomProps(properties);
query.setTaskName(WorkflowModelModeratedInvitation.WF_REVIEW_TASK);
// query for invite workflow tasks
List<WorkflowTask> results = new ArrayList<WorkflowTask>();
if(workflowAdminService.isEngineEnabled(JBPMEngine.ENGINE_ID))
{
query.setTaskName(WorkflowModelModeratedInvitation.WF_REVIEW_TASK);
List<WorkflowTask> jbpmTasks = this.workflowService.queryTasks(query, true);
if(jbpmTasks !=null)
{
results.addAll(jbpmTasks);
}
}
if(workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID))
{
query.setTaskName(WorkflowModelModeratedInvitation.WF_ACTIVITI_REVIEW_TASK);
List<WorkflowTask> jbpmTasks = this.workflowService.queryTasks(query, true);
if(jbpmTasks !=null)
{
results.addAll(jbpmTasks);
}
}
if (logger.isDebugEnabled())
{
logger.debug(" searchModeratedInvitations in "+ (System.currentTimeMillis()-start) + " ms");
}
return results;
}
private List<WorkflowTask> searchNominatedInvitations(InvitationSearchCriteria criteria)
{
long start = (logger.isDebugEnabled()) ? System.currentTimeMillis() : 0;
WorkflowTaskQuery query = new WorkflowTaskQuery();
query.setTaskState(WorkflowTaskState.IN_PROGRESS);
String invitee = criteria.getInvitee();
if(invitee != null)
{
query.setActorId(invitee);
}
Map<QName, Object> queryProps = new HashMap<QName, Object>();
String inviter = criteria.getInviter();
if (inviter != null)
{
queryProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITER_USER_NAME, inviter);
}
String resourceName = criteria.getResourceName();
if (resourceName != null)
{
queryProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_NAME, resourceName);
}
//TODO uncomment if more ResourceTypes are created.
// ResourceType resourceType = criteria.getResourceType();
// if (resourceType != null)
// {
// wfNominatedQueryProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TYPE,
// resourceType.name());
// }
// set workflow task query parameters
query.setProcessCustomProps(queryProps);
List<WorkflowTask> results = new ArrayList<WorkflowTask>();
if(workflowAdminService.isEngineEnabled(JBPMEngine.ENGINE_ID))
{
query.setTaskName(WorkflowModelNominatedInvitation.WF_TASK_INVITE_PENDING);
List<WorkflowTask> jbpmTasks = this.workflowService.queryTasks(query, true);
if(jbpmTasks !=null)
{
results.addAll(jbpmTasks);
}
}
if(workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID))
{
query.setTaskName(WorkflowModelNominatedInvitation.WF_TASK_ACTIVIT_INVITE_PENDING);
List<WorkflowTask> jbpmTasks = this.workflowService.queryTasks(query, true);
if(jbpmTasks !=null)
{
results.addAll(jbpmTasks);
}
}
if (logger.isDebugEnabled())
{
logger.debug(" searchNominatedInvitations in "+ (System.currentTimeMillis()-start) + " ms");
}
return results;
}
// Implementation methods below
/**
* Set the workflow service
*
* @param workflowService
*/
public void setWorkflowService(WorkflowService workflowService)
{
this.workflowService = workflowService;
}
/**
* @param workflowAdminService the workflowAdminService to set
*/
public void setWorkflowAdminService(WorkflowAdminService workflowAdminService)
{
this.workflowAdminService = workflowAdminService;
}
/**
* @return the workflow service
*/
public WorkflowService getWorkflowService()
{
return workflowService;
}
/**
* @param actionService the actionService to set
*/
public void setActionService(ActionService actionService)
{
this.actionService = actionService;
}
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
public PersonService getPersonService()
{
return personService;
}
public void setSiteService(SiteService siteService)
{
this.siteService = siteService;
}
public SiteService getSiteService()
{
return siteService;
}
public void setAuthenticationService(MutableAuthenticationService authenticationService)
{
this.authenticationService = authenticationService;
}
public MutableAuthenticationService getAuthenticationService()
{
return authenticationService;
}
public void setUserNameGenerator(UserNameGenerator usernameGenerator)
{
this.usernameGenerator = usernameGenerator;
}
public UserNameGenerator getUserNameGenerator()
{
return usernameGenerator;
}
public void setPasswordGenerator(PasswordGenerator passwordGenerator)
{
this.passwordGenerator = passwordGenerator;
}
public PasswordGenerator getPasswordGenerator()
{
return passwordGenerator;
}
public void setDictionaryService(DictionaryService dictionaryService)
{
this.dictionaryService = dictionaryService;
}
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
public NamespaceService getNamespaceService()
{
return namespaceService;
}
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
public PermissionService getPermissionService()
{
return permissionService;
}
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
public NodeService getNodeService()
{
return nodeService;
}
/**
* Creates a person for the invitee with a generated user name.
*
* @param inviteeFirstName first name of invitee
* @param inviteeLastName last name of invitee
* @param inviteeEmail email address of invitee
* @return invitee user name
*/
private String createInviteePerson(String inviteeFirstName, String inviteeLastName, String inviteeEmail)
{
// Attempt to generate user name for invitee
// which does not belong to an existing person
// Tries up to MAX_NUM_INVITEE_USER_NAME_GEN_TRIES
// at which point a web script exception is thrown
String inviteeUserName = null;
int i = 0;
do
{
inviteeUserName = usernameGenerator.generateUserName(inviteeFirstName, inviteeLastName, inviteeEmail, i);
i++;
} while (this.personService.personExists(inviteeUserName) && (i < getMaxUserNameGenRetries()));
// if after 10 tries is not able to generate a user name for a
// person who doesn't already exist, then throw a web script exception
if (this.personService.personExists(inviteeUserName))
{
logger.debug("Failed - unable to generate username for invitee.");
Object[] objs = { inviteeFirstName, inviteeLastName, inviteeEmail };
throw new InvitationException("invitation.invite.unable_generate_id", objs);
}
// create a person node for the invitee with generated invitee user name
// and other provided person property values
final Map<QName, Serializable> properties = new HashMap<QName, Serializable>();
properties.put(ContentModel.PROP_USERNAME, inviteeUserName);
properties.put(ContentModel.PROP_FIRSTNAME, inviteeFirstName);
properties.put(ContentModel.PROP_LASTNAME, inviteeLastName);
properties.put(ContentModel.PROP_EMAIL, inviteeEmail);
final String finalUserName = inviteeUserName;
AuthenticationUtil.runAs(new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
NodeRef person = personService.createPerson(properties);
permissionService.setPermission(person, finalUserName, PermissionService.ALL_PERMISSIONS, true);
return null;
}
}, AuthenticationUtil.getSystemUserName());
return inviteeUserName;
}
/**
* Creates a disabled user account for the given invitee user name with a
* generated password
*
* @param inviteeUserName
* @return password generated for invitee user account
*/
private String createInviteeDisabledAccount(String inviteeUserName)
{
// generate password using password generator
char[] generatedPassword = passwordGenerator.generatePassword().toCharArray();
// create disabled user account for invitee user name with generated
// password
this.authenticationService.createAuthentication(inviteeUserName, generatedPassword);
this.authenticationService.setAuthenticationEnabled(inviteeUserName, false);
return String.valueOf(generatedPassword);
}
/**
* Moderated invitation implementation
*
* @param inviteeComments
* @param inviteeUserName
* @param resourceType
* @param resourceName
* @param inviteeRole
* @return the new moderated invitation
*/
private ModeratedInvitation startModeratedInvite(String inviteeComments, String inviteeUserName,
Invitation.ResourceType resourceType, String resourceName, String inviteeRole)
{
// Get invitee person NodeRef to add as assignee
NodeRef inviteeNodeRef = personService.getPerson(inviteeUserName);
SiteInfo siteInfo = siteService.getSite(resourceName);
if (siteService.isMember(resourceName, inviteeUserName))
{
if (logger.isDebugEnabled())
logger.debug("Failed - invitee user is already a member of the site.");
Object objs[] = { inviteeUserName, "", resourceName };
throw new InvitationExceptionUserError("invitation.invite.already_member", objs);
}
String roleGroup = siteService.getSiteRoleGroup(resourceName, SiteModel.SITE_MANAGER);
// get the workflow description
String workflowDescription = generateWorkflowDescription(siteInfo, "invitation.moderated.workflow.description");
Map<QName, Serializable> workflowProps = new HashMap<QName, Serializable>(16);
workflowProps.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, workflowDescription);
workflowProps.put(WorkflowModelModeratedInvitation.ASSOC_GROUP_ASSIGNEE, roleGroup);
workflowProps.put(WorkflowModelModeratedInvitation.WF_PROP_INVITEE_COMMENTS, inviteeComments);
workflowProps.put(WorkflowModelModeratedInvitation.WF_PROP_INVITEE_ROLE, inviteeRole);
workflowProps.put(WorkflowModelModeratedInvitation.WF_PROP_INVITEE_USER_NAME, inviteeUserName);
workflowProps.put(WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_NAME, resourceName);
workflowProps.put(WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_TYPE, resourceType.toString());
// get the moderated workflow
WorkflowDefinition wfDefinition = getWorkflowDefinition(false);
return (ModeratedInvitation) startWorkflow(wfDefinition, workflowProps);
}
/**
* Starts the Invite workflow
*
* @param inviteeFirstName first name of invitee
* @param inviteeLastNamme last name of invitee
* @param inviteeEmail email address of invitee
* @param siteShortName short name of site that the invitee is being invited
* to by the inviter
* @param inviteeSiteRole role under which invitee is being invited to the
* site by the inviter
* @param serverPath externally accessible server address of server hosting
* invite web scripts
*/
private NominatedInvitation startNominatedInvite(String inviteeFirstName, String inviteeLastName,
String inviteeEmail, String inviteeUserName, Invitation.ResourceType resourceType,
String siteShortName, String inviteeSiteRole, String serverPath, String acceptUrl, String rejectUrl)
{
// get the inviter user name (the name of user web script is executed
// under)
String inviterUserName = authenticationService.getCurrentUserName();
boolean created = false;
checkManagerRole(inviterUserName, resourceType, siteShortName);
if (logger.isDebugEnabled())
{
logger.debug("startInvite() inviterUserName=" + inviterUserName + " inviteeUserName=" + inviteeUserName
+ " inviteeFirstName=" + inviteeFirstName + " inviteeLastName=" + inviteeLastName
+ " inviteeEmail=" + inviteeEmail + " siteShortName=" + siteShortName + " inviteeSiteRole="
+ inviteeSiteRole);
}
//
// if we have not explicitly been passed an existing user's user name
// then ....
//
// if a person already exists who has the given invitee email address
//
// 1) obtain invitee user name from first person found having the
// invitee email address, first name and last name
// 2) handle error conditions -
// (invitee already has an invitation in progress for the given site,
// or he/she is already a member of the given site
//
if (inviteeUserName == null || inviteeUserName.trim().length() == 0)
{
inviteeUserName = null;
Set<NodeRef> peopleWithInviteeEmail = personService.getPeopleFilteredByProperty(ContentModel.PROP_EMAIL, inviteeEmail);
if (peopleWithInviteeEmail.size() > 0)
{
// get person already existing who has the given
// invitee email address
for (NodeRef personRef : peopleWithInviteeEmail)
{
Serializable firstNameVal = this.getNodeService().getProperty(personRef,
ContentModel.PROP_FIRSTNAME);
Serializable lastNameVal = this.getNodeService().getProperty(personRef, ContentModel.PROP_LASTNAME);
String personFirstName = DefaultTypeConverter.INSTANCE.convert(String.class, firstNameVal);
String personLastName = DefaultTypeConverter.INSTANCE.convert(String.class, lastNameVal);
if (personFirstName != null && personFirstName.equalsIgnoreCase(inviteeFirstName))
{
if (personLastName != null && personLastName.equalsIgnoreCase(inviteeLastName))
{
// got a match on email, lastname, firstname
// get invitee user name of that person
Serializable userNamePropertyVal = this.getNodeService().getProperty(personRef,
ContentModel.PROP_USERNAME);
inviteeUserName = DefaultTypeConverter.INSTANCE.convert(String.class, userNamePropertyVal);
if (logger.isDebugEnabled())
{
logger
.debug("not explictly passed username - found matching email, resolved inviteeUserName="
+ inviteeUserName);
}
}
}
}
}
if (inviteeUserName == null)
{
// This shouldn't normally happen. Due to the fix for ETHREEOH-3268, the link to invite external users
// should be disabled when the authentication chain does not allow it.
if (!authenticationService.isAuthenticationCreationAllowed())
{
throw new InvitationException("invitation.invite.authentication_chain");
}
// else there are no existing people who have the given invitee
// email address so create new person
inviteeUserName = createInviteePerson(inviteeFirstName, inviteeLastName, inviteeEmail);
created = true;
if (logger.isDebugEnabled())
{
logger.debug("not explictly passed username - created new person, inviteeUserName="
+ inviteeUserName);
}
}
}
/**
* throw exception if person is already a member of the given site
*/
if (this.siteService.isMember(siteShortName, inviteeUserName))
{
if (logger.isDebugEnabled())
logger.debug("Failed - invitee user is already a member of the site.");
Object objs[] = { inviteeUserName, inviteeEmail, siteShortName };
throw new InvitationExceptionUserError("invitation.invite.already_member", objs);
}
//
// If a user account does not already exist for invitee user name
// then create a disabled user account for the invitee.
// Hold a local reference to generated password if disabled invitee
// account
// is created, otherwise if a user account already exists for invitee
// user name, then local reference to invitee password will be "null"
//
final String initeeUserNameFinal = inviteeUserName;
String inviteePassword = created ? AuthenticationUtil.runAs(new RunAsWork<String>()
{
public String doWork()
{
return createInviteeDisabledAccount(initeeUserNameFinal);
}
}, AuthenticationUtil.getSystemUserName()) : null;
// create a ticket for the invite - this is used
String inviteTicket = GUID.generate();
//
// Start the invite workflow with inviter, invitee and site properties
//
WorkflowDefinition wfDefinition = getWorkflowDefinition(true);
// Get invitee person NodeRef to add as assignee
NodeRef inviteeNodeRef = personService.getPerson(inviteeUserName);
SiteInfo siteInfo = this.siteService.getSite(siteShortName);
String siteDescription = siteInfo.getDescription();
if (siteDescription == null)
{
siteDescription = "";
}
else if (siteDescription.length() > 255)
{
siteDescription = siteDescription.substring(0, 255);
}
// get the workflow description
String workflowDescription = generateWorkflowDescription(siteInfo, "invitation.nominated.workflow.description");
// create workflow properties
Map<QName, Serializable> workflowProps = new HashMap<QName, Serializable>(32);
workflowProps.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, workflowDescription);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITER_USER_NAME, inviterUserName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_USER_NAME, inviteeUserName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_EMAIL, inviteeEmail);
workflowProps.put(WorkflowModel.ASSOC_ASSIGNEE, inviteeNodeRef);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_FIRSTNAME, inviteeFirstName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_LASTNAME, inviteeLastName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_GEN_PASSWORD, inviteePassword);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_NAME, siteShortName);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TITLE, siteInfo.getTitle());
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_DESCRIPTION, siteDescription);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_RESOURCE_TYPE, resourceType.toString());
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITEE_ROLE, inviteeSiteRole);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_SERVER_PATH, serverPath);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_ACCEPT_URL, acceptUrl);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_REJECT_URL, rejectUrl);
workflowProps.put(WorkflowModelNominatedInvitation.WF_PROP_INVITE_TICKET, inviteTicket);
return (NominatedInvitation) startWorkflow(wfDefinition, workflowProps);
}
private Invitation startWorkflow(WorkflowDefinition wfDefinition, Map<QName, Serializable> workflowProps)
{
NodeRef wfPackage = workflowService.createPackage(null);
workflowProps.put(WorkflowModel.ASSOC_PACKAGE, wfPackage);
// start the workflow
WorkflowPath wfPath = this.workflowService.startWorkflow(wfDefinition.getId(), workflowProps);
//
// complete invite workflow start task to send out the invite email
//
// get the workflow tasks
String workflowId = wfPath.getInstance().getId();
WorkflowTask startTask = workflowService.getStartTask(workflowId);
// attach empty package to start task, end it and follow with transition
// that sends out the invite
if (logger.isDebugEnabled())
logger.debug("Starting Invite workflow task by attaching empty package...");
if (logger.isDebugEnabled())
logger.debug("Transitioning Invite workflow task...");
try
{
workflowService.endTask(startTask.getId(), null);
}
catch (RuntimeException err)
{
if (logger.isDebugEnabled())
logger.debug("Failed - caught error during Invite workflow transition: " + err.getMessage());
throw err;
}
Invitation invitation = getInvitation(startTask);
return invitation;
}
/**
* Return Activiti workflow definition unless Activiti engine is disabled.
* @param isNominated TODO
* @return
*/
private WorkflowDefinition getWorkflowDefinition(boolean isNominated)
{
String workflowName = isNominated ? getNominatedDefinitionName() : getModeratedDefinitionName();
WorkflowDefinition definition = workflowService.getDefinitionByName(workflowName);
if (definition == null)
{
// handle workflow definition does not exist
Object objs[] = {workflowName};
throw new InvitationException("invitation.error.noworkflow", objs);
}
return definition;
}
private String getNominatedDefinitionName()
{
if(workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID))
{
return WorkflowModelNominatedInvitation.WORKFLOW_DEFINITION_NAME_ACTIVITI;
}
else if(workflowAdminService.isEngineEnabled(JBPMEngine.ENGINE_ID))
{
return WorkflowModelNominatedInvitation.WORKFLOW_DEFINITION_NAME;
}
throw new IllegalStateException("None of the Workflow engines supported by teh InvitationService are currently enabled!");
}
private String getModeratedDefinitionName()
{
if(workflowAdminService.isEngineEnabled(ActivitiConstants.ENGINE_ID))
{
return WorkflowModelModeratedInvitation.WORKFLOW_DEFINITION_NAME_ACTIVITI;
}
else if(workflowAdminService.isEngineEnabled(JBPMEngine.ENGINE_ID))
{
return WorkflowModelModeratedInvitation.WORKFLOW_DEFINITION_NAME;
}
throw new IllegalStateException("None of the Workflow engines supported by teh InvitationService are currently enabled!");
}
/**
* Check that the specified user has manager role over the resource.
*
* @param userId
* @throws InvitationException
*/
private void checkManagerRole(String userId, Invitation.ResourceType resourceType, String siteShortName)
{
// if inviter is not the site manager then throw web script exception
String inviterRole = this.siteService.getMembersRole(siteShortName, userId);
if ((inviterRole == null) || (inviterRole.equals(SiteModel.SITE_MANAGER) == false))
{
Object objs[] = { userId, siteShortName };
throw new InvitationExceptionForbidden("invitation.invite.not_site_manager", objs);
}
}
/**
* Validator for invitationId
*
* @param invitationId
*/
private void validateInvitationId(String invitationId)
{
final String ID_SEPERATOR_REGEX = "\\$";
String[] parts = invitationId.split(ID_SEPERATOR_REGEX);
if (parts.length != 2)
{
Object objs[] = { invitationId };
throw new InvitationExceptionUserError("invitation.error.invalid_inviteId_format", objs);
}
}
private int getMaxUserNameGenRetries()
{
return maxUserNameGenRetries;
}
/**
* NodeServicePolicies.BeforeDeleteNodePolicy Called immediatly prior to
* deletion of a web site.
*/
public void beforeDeleteNode(NodeRef nodeRef)
{
logger.debug("beforeDeleteNode");
final NodeRef siteRef = nodeRef;
// Run as system user
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>()
{
public Object doWork() throws Exception
{
QName type = nodeService.getType(siteRef);
if (dictionaryService.isSubClass(type, SiteModel.TYPE_SITE))
{
// this is a web site being deleted.
String siteName = (String) nodeService.getProperty(siteRef, ContentModel.PROP_NAME);
if (siteName != null)
{
long start =0;
if (logger.isDebugEnabled())
{
logger.debug("Invitation service delete node fired " + type + ", " + siteName);
start = System.currentTimeMillis();
}
InvitationSearchCriteriaImpl criteria =
getPendingInvitationCriteriaForResource(Invitation.ResourceType.WEB_SITE, siteName);
List<String> invitationIds = searchInvitationsForIds(criteria, -1);
if (logger.isDebugEnabled())
{
long end = System.currentTimeMillis();
logger.debug("Invitations found: " + invitationIds.size() + " in "+ ((end-start)/1000) + " seconds");
start = System.currentTimeMillis();
}
// Create the action
Action action = actionService.createAction(CancelWorkflowActionExecuter.NAME);
action.setParameterValue(CancelWorkflowActionExecuter.PARAM_WORKFLOW_ID_LIST, (Serializable)invitationIds);
// Cancel the workflows asynchronously
actionService.executeAction(action, siteRef, false, true);
if (logger.isDebugEnabled())
{
long end = System.currentTimeMillis();
logger.debug("Invitations cancelled: " + invitationIds.size() + " in "+ (end-start) + " ms");
}
}
}
else if (dictionaryService.isSubClass(type, ContentModel.TYPE_PERSON))
{
// this is a user being deleted.
String userName = (String) nodeService.getProperty(siteRef, ContentModel.PROP_USERNAME);
invalidateTasksByUser(userName);
}
return null;
}
}, AuthenticationUtil.SYSTEM_USER_NAME);
}
private void invalidateTasksByUser(String userName) throws AuthenticationException
{
List<Invitation> listForInvitee = listPendingInvitationsForInvitee(userName);
for (Invitation inv : listForInvitee)
{
cancel(inv.getInviteId());
}
}
/**
* Generates a description for the workflow
*
* @param siteInfo The site to generate a description for
* @param messageId The resource bundle key to use for the description
* @return The workflow description
*/
protected String generateWorkflowDescription(SiteInfo siteInfo, String messageId)
{
String siteTitle = siteInfo.getTitle();
if (siteTitle == null || siteTitle.length() == 0)
{
siteTitle = siteInfo.getShortName();
}
return I18NUtil.getMessage(messageId, siteTitle);
}
/**
* @param sendEmails the sendEmails to set
*/
public void setSendEmails(boolean sendEmails)
{
this.sendEmails = sendEmails;
}
/**
* @return true if emails are sent on invite.
*/
public boolean isSendEmails()
{
return sendEmails;
}
public void setSysAdminParams(SysAdminParams sysAdminParams)
{
this.sysAdminParams = sysAdminParams;
}
}