MNT-17427 : api/invite/cancel deletes records in the database with a GET: CSRF/XSS attack

- delete the script/org/alfresco/repository/invite/invite.get
   - use the alternatives: script/org/alfresco/repository/site/invitation/invitation.post and script/org/alfresco/repository/site/invitation/invitation.delete
   - updating the tests
   - updating the controller for the invitation.delete to a java controller
   - fix test fallout (SiteServiceTest testInviteDisabledUser - expected error status code)
   - improve security by allowing only invitationIDs that belong the the site passed as parameter to be canceled
   - be consistent and return 404 when an invitationID can not be found

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@135255 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Andrei Rebegea
2017-02-20 09:04:45 +00:00
parent cff630061f
commit 02928e266a
12 changed files with 665 additions and 780 deletions

View File

@@ -0,0 +1,159 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* 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/>.
* #L%
*/
package org.alfresco.repo.web.scripts.invitation;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.service.cmr.invitation.Invitation;
import org.alfresco.service.cmr.invitation.InvitationExceptionForbidden;
import org.alfresco.service.cmr.invitation.InvitationService;
import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
* Cancel invitation for a web site; This is the controller for the
* org/alfresco/repository/site/invitation/invitation.delete.desc.xml webscript
*/
public class InvitationDelete extends DeclarativeWebScript
{
// services
private InvitationService invitationService;
private SiteService siteService;
public void setInvitationService(InvitationService invitationService)
{
this.invitationService = invitationService;
}
public void setSiteService(SiteService siteService)
{
this.siteService = siteService;
}
@Override
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
{
Map<String, Object> model = new HashMap<String, Object>();
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
final String siteShortName = templateVars.get("shortname");
final String invitationId = templateVars.get("invitationId");
validateParameters(siteShortName, invitationId);
try
{
// MNT-9905 Pending Invites created by one site manager aren't visible to other site managers
String currentUser = AuthenticationUtil.getRunAsUser();
if (siteShortName != null && (SiteModel.SITE_MANAGER).equals(siteService.getMembersRole(siteShortName, currentUser)))
{
RunAsWork<Void> runAsSystem = new RunAsWork<Void>()
{
@Override
public Void doWork() throws Exception
{
checkAndCancelTheInvitation(invitationId, siteShortName);
return null;
}
};
AuthenticationUtil.runAs(runAsSystem, AuthenticationUtil.getSystemUserName());
}
else
{
checkAndCancelTheInvitation(invitationId, siteShortName);
}
}
catch (InvitationExceptionForbidden fe)
{
throw new WebScriptException(Status.STATUS_FORBIDDEN, "Unable to cancel workflow", fe);
}
catch (AccessDeniedException ade)
{
throw new WebScriptException(Status.STATUS_FORBIDDEN, "Unable to cancel workflow", ade);
}
return model;
}
private void validateParameters(String siteShortName, String invitationId)
{
if ((invitationId == null) || (invitationId.length() == 0))
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Invalid invitation id provided");
}
SiteInfo site = siteService.getSite(siteShortName);
if (site == null)
{
throw new WebScriptException(Status.STATUS_NOT_FOUND, "Invalid site id provided");
}
}
protected void checkAndCancelTheInvitation(final String invId, String siteShortName)
{
Invitation invitation = null;
try
{
invitation = invitationService.getInvitation(invId);
}
catch (org.alfresco.service.cmr.invitation.InvitationExceptionNotFound ienf)
{
throwInvitationNotFoundException(invId, siteShortName);
}
if (invitation == null)
{
throwInvitationNotFoundException(invId, siteShortName);
}
// check that this invitation really belongs to the specified siteShortName
if (invitation != null && invitation.getResourceName() != null && !siteShortName.equals(invitation.getResourceName()))
{
throw new WebScriptException(Status.STATUS_FORBIDDEN, "Unable to cancel workflow");
}
invitationService.cancel(invId);
}
protected void throwInvitationNotFoundException(final String invId, String siteShortName)
{
throw new WebScriptException(Status.STATUS_NOT_FOUND,
"The invitation :" + invId + " for web site :" + siteShortName + ", does not exist.");
}
}

View File

@@ -1,324 +0,0 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited
* %%
* This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* 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/>.
* #L%
*/
package org.alfresco.repo.web.scripts.invite;
import java.util.HashMap;
import java.util.Map;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.service.cmr.invitation.Invitation;
import org.alfresco.service.cmr.invitation.InvitationExceptionForbidden;
import org.alfresco.service.cmr.invitation.InvitationExceptionUserError;
import org.alfresco.service.cmr.invitation.InvitationService;
import org.alfresco.service.cmr.invitation.NominatedInvitation;
import org.alfresco.service.cmr.site.SiteService;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
/**
* Web Script invoked by a Site Manager (Inviter) to either send
* (action='start') an invitation to a another person (Invitee) to join a Site
* as a Site Collaborator, or to cancel (action='cancel') a pending invitation
* that has already been sent out
*
* @author glen dot johnson at alfresco dot com
*/
public class Invite extends DeclarativeWebScript
{
private static final String ACTION_START = "start";
private static final String ACTION_CANCEL = "cancel";
private static final String MODEL_PROP_KEY_ACTION = "action";
private static final String MODEL_PROP_KEY_INVITE_ID = "inviteId";
private static final String MODEL_PROP_KEY_INVITE_TICKET = "inviteTicket";
private static final String MODEL_PROP_KEY_INVITEE_USER_NAME = "inviteeUserName";
private static final String MODEL_PROP_KEY_INVITEE_FIRSTNAME = "inviteeFirstName";
private static final String MODEL_PROP_KEY_INVITEE_LASTNAME = "inviteeLastName";
private static final String MODEL_PROP_KEY_INVITEE_EMAIL = "inviteeEmail";
private static final String MODEL_PROP_KEY_SITE_SHORT_NAME = "siteShortName";
private static final String MODEL_PROP_KEY_INVITEE_USERNAME = "inviteeUserName";
// URL request parameter names
private static final String PARAM_INVITEE_FIRSTNAME = "inviteeFirstName";
private static final String PARAM_INVITEE_LASTNAME = "inviteeLastName";
private static final String PARAM_INVITEE_EMAIL = "inviteeEmail";
private static final String PARAM_SITE_SHORT_NAME = "siteShortName";
private static final String PARAM_INVITE_ID = "inviteId";
private static final String PARAM_INVITEE_SITE_ROLE = "inviteeSiteRole";
private static final String PARAM_SERVER_PATH = "serverPath";
private static final String PARAM_ACCEPT_URL = "acceptUrl";
private static final String PARAM_REJECT_URL = "rejectUrl";
// services
private InvitationService invitationService;
private SiteService siteService;
public void setInvitationService(InvitationService invitationService)
{
this.invitationService = invitationService;
}
public void setSiteService(SiteService siteService)
{
this.siteService = siteService;
}
/*
* (non-Javadoc)
*
* @see
* org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco
* .web.scripts.WebScriptRequest,
* org.alfresco.web.scripts.WebScriptResponse)
*/
@Override
protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache)
{
// initialise model to pass on for template to render
Map<String, Object> model = new HashMap<String, Object>();
// extract action string from URL
String servicePath = req.getServicePath();
String action = null;
int actionStartIndex = servicePath.lastIndexOf("/") + 1;
if (actionStartIndex <= servicePath.length() - 1)
{
action = servicePath.substring(actionStartIndex, servicePath
.length());
}
// check that the action has been provided on the URL
// and that URL parameters have been provided
if ((action == null) || (action.length() == 0))
{
// handle action not provided on URL
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"Action has not been provided in URL");
}
// handle no parameters given on URL
if ((req.getParameterNames() == null) || (req.getParameterNames().length == 0))
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"No parameters have been provided on URL");
}
// handle action 'start'
if (action.equals(ACTION_START))
{
// check for 'inviteeFirstName' parameter not provided
String inviteeFirstName = req.getParameter(PARAM_INVITEE_FIRSTNAME);
if ((inviteeFirstName == null) || (inviteeFirstName.trim().length() == 0))
{
// handle inviteeFirstName URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'inviteeFirstName' parameter "
+ "has not been provided in URL for action '"
+ ACTION_START + "'");
}
// check for 'inviteeLastName' parameter not provided
String inviteeLastName = req.getParameter(PARAM_INVITEE_LASTNAME);
if ((inviteeLastName == null) || (inviteeLastName.trim().length() == 0))
{
// handle inviteeLastName URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'inviteeLastName' parameter "
+ "has not been provided in URL for action '"
+ ACTION_START + "'");
}
// check for 'inviteeEmail' parameter not provided
String inviteeEmail = req.getParameter(PARAM_INVITEE_EMAIL);
if ((inviteeEmail == null) || (inviteeEmail.trim().length() == 0))
{
// handle inviteeEmail URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'inviteeEmail' parameter "
+ "has not been provided in URL for action '"
+ ACTION_START + "'");
}
// check for 'siteShortName' parameter not provided
String siteShortName = req.getParameter(PARAM_SITE_SHORT_NAME);
if ((siteShortName == null) || (siteShortName.trim().length() == 0))
{
// handle siteShortName URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'siteShortName' parameter "
+ "has not been provided in URL for action '"
+ ACTION_START + "'");
}
// check for 'inviteeSiteRole' parameter not provided
String inviteeSiteRole = req.getParameter(PARAM_INVITEE_SITE_ROLE);
if ((inviteeSiteRole == null) || (inviteeSiteRole.trim().length() == 0))
{
// handle inviteeSiteRole URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'inviteeSiteRole' parameter has not been provided in URL for action '"
+ ACTION_START + "'");
}
// check for 'serverPath' parameter not provided
String serverPath = req.getParameter(PARAM_SERVER_PATH);
if ((serverPath == null) || (serverPath.trim().length() == 0))
{
// handle serverPath URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'serverPath' parameter has not been provided in URL for action '"
+ ACTION_START + "'");
}
// check for 'acceptUrl' parameter not provided
String acceptUrl = req.getParameter(PARAM_ACCEPT_URL);
if ((acceptUrl == null) || (acceptUrl.trim().length() == 0))
{
// handle acceptUrl URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'acceptUrl' parameter has not been provided in URL for action '"
+ ACTION_START + "'");
}
// check for 'rejectUrl' parameter not provided
String rejectUrl = req.getParameter(PARAM_REJECT_URL);
if ((rejectUrl == null) || (rejectUrl.trim().length() == 0))
{
// handle rejectUrl URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'rejectUrl' parameter has not been provided in URL for action '"
+ ACTION_START + "'");
}
// check for the invitee user name (if present)
String inviteeUserName = req.getParameter(MODEL_PROP_KEY_INVITEE_USERNAME);
NominatedInvitation newInvite = null;
try
{
if (inviteeUserName != null)
{
newInvite = invitationService.inviteNominated(inviteeUserName, Invitation.ResourceType.WEB_SITE, siteShortName, inviteeSiteRole, acceptUrl, rejectUrl);
}
else
{
newInvite = invitationService.inviteNominated(inviteeFirstName, inviteeLastName, inviteeEmail, Invitation.ResourceType.WEB_SITE, siteShortName, inviteeSiteRole, acceptUrl, rejectUrl);
}
// add model properties for template to render
model.put(MODEL_PROP_KEY_ACTION, ACTION_START);
model.put(MODEL_PROP_KEY_INVITE_ID, newInvite.getInviteId());
model.put(MODEL_PROP_KEY_INVITE_TICKET, newInvite.getTicket());
model.put(MODEL_PROP_KEY_INVITEE_USER_NAME, newInvite.getInviteeUserName());
model.put(MODEL_PROP_KEY_INVITEE_FIRSTNAME, inviteeFirstName);
model.put(MODEL_PROP_KEY_INVITEE_LASTNAME, inviteeLastName);
model.put(MODEL_PROP_KEY_INVITEE_EMAIL, inviteeEmail);
model.put(MODEL_PROP_KEY_SITE_SHORT_NAME, siteShortName);
}
catch (InvitationExceptionUserError ie)
{
throw new WebScriptException(Status.STATUS_CONFLICT, ie.getMessage());
}
catch (InvitationExceptionForbidden fe)
{
throw new WebScriptException(Status.STATUS_FORBIDDEN, fe.toString());
}
// process action 'start' with provided parameters
//startInvite(model, inviteeFirstName, inviteeLastName, inviteeEmail, inviteeUserName, siteShortName, inviteeSiteRole, serverPath, acceptUrl, rejectUrl);
}
// else handle if provided 'action' is 'cancel'
else if (action.equals(ACTION_CANCEL))
{
// check for 'inviteId' parameter not provided
String inviteId = req.getParameter(PARAM_INVITE_ID);
if ((inviteId == null) || (inviteId.length() == 0))
{
// handle inviteId URL parameter not provided
throw new WebScriptException(Status.STATUS_BAD_REQUEST,
"'inviteId' parameter has "
+ "not been provided in URL for action '"
+ ACTION_CANCEL + "'");
}
// process action 'cancel' with provided parameters
try
{
//MNT-9905 Pending Invites created by one site manager aren't visible to other site managers
String currentUser = AuthenticationUtil.getRunAsUser();
String siteShortName = req.getParameter(PARAM_SITE_SHORT_NAME);
if (siteShortName != null && (SiteModel.SITE_MANAGER).equals(siteService.getMembersRole(siteShortName, currentUser)))
{
final String invId = inviteId;
RunAsWork<Void> runAsSystem = new RunAsWork<Void>()
{
@Override
public Void doWork() throws Exception
{
invitationService.cancel(invId);
return null;
}
};
AuthenticationUtil.runAs(runAsSystem, AuthenticationUtil.getSystemUserName());
}
else
{
invitationService.cancel(inviteId);
}
// add model properties for template to render
model.put(MODEL_PROP_KEY_ACTION, ACTION_CANCEL);
model.put(MODEL_PROP_KEY_INVITE_ID, inviteId);
}
catch(InvitationExceptionForbidden fe)
{
throw new WebScriptException(Status.STATUS_FORBIDDEN, "Unable to cancel workflow" , fe);
}
catch(AccessDeniedException ade)
{
throw new WebScriptException(Status.STATUS_FORBIDDEN, "Unable to cancel workflow" , ade);
}
}
// handle action not recognised
else
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Action, '"
+ action + "', "
+ "provided in URL has not been recognised.");
}
return model;
}
}