Use SecurityID in destructive campaign actions

Using POST rather than PUT because SecurityToken->checkRequest()
doesn't accept PUT data (it's only in the request body, not
in $_POST and HTTPRequest->requestVars()).
This commit is contained in:
Ingo Schommer 2016-04-18 08:26:24 +12:00 committed by Damian Mooyman
parent 572d8427e0
commit e840ba805e
3 changed files with 25 additions and 10 deletions

View File

@ -27,9 +27,9 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
private static $url_handlers = [ private static $url_handlers = [
'GET sets' => 'readCampaigns', 'GET sets' => 'readCampaigns',
'POST set/$ID/publish' => 'publishCampaign',
'POST set/$ID' => 'createCampaign', 'POST set/$ID' => 'createCampaign',
'GET set/$ID/$Name' => 'readCampaign', 'GET set/$ID/$Name' => 'readCampaign',
'PUT set/$ID/publish' => 'publishCampaign',
'PUT set/$ID' => 'updateCampaign', 'PUT set/$ID' => 'updateCampaign',
'DELETE set/$ID' => 'deleteCampaign', 'DELETE set/$ID' => 'deleteCampaign',
]; ];
@ -66,7 +66,7 @@ class CampaignAdmin extends LeftAndMain implements PermissionProvider {
'itemListViewEndpoint' => $this->Link('set/:id/show'), 'itemListViewEndpoint' => $this->Link('set/:id/show'),
'publishEndpoint' => [ 'publishEndpoint' => [
'url' => $this->Link('set/:id/publish'), 'url' => $this->Link('set/:id/publish'),
'method' => 'put' 'method' => 'post'
] ]
]); ]);
} }
@ -431,6 +431,12 @@ JSON;
* @return SS_HTTPResponse * @return SS_HTTPResponse
*/ */
public function publishCampaign(SS_HTTPRequest $request) { public function publishCampaign(SS_HTTPRequest $request) {
// Protect against CSRF on destructive action
if(!SecurityToken::inst()->checkRequest($request)) {
return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))
->addHeader('Content-Type', 'application/json');
}
$id = $request->param('ID'); $id = $request->param('ID');
if(!$id || !is_numeric($id)) { if(!$id || !is_numeric($id)) {
return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400)) return (new SS_HTTPResponse(json_encode(['status' => 'error']), 400))

View File

@ -194,6 +194,10 @@ class LeftAndMain extends Controller implements PermissionProvider {
$combinedClientConfig['sections'][$className] = Injector::inst()->get($className)->getClientConfig(); $combinedClientConfig['sections'][$className] = Injector::inst()->get($className)->getClientConfig();
} }
// Get "global" CSRF token for use in JavaScript
$token = new SecurityToken();
$combinedClientConfig[$token->getName()] = $token->getValue();
return Convert::raw2json($combinedClientConfig); return Convert::raw2json($combinedClientConfig);
} }

View File

@ -18,8 +18,9 @@ class CampaignAdminContainer extends SilverStripeComponent {
this.addCampaign = this.addCampaign.bind(this); this.addCampaign = this.addCampaign.bind(this);
this.createFn = this.createFn.bind(this); this.createFn = this.createFn.bind(this);
this.publishApi = backend.createEndpointFetcher({ this.publishApi = backend.createEndpointFetcher({
url: this.props.config.publishEndpoint.url, url: this.props.sectionConfig.publishEndpoint.url,
method: this.props.config.publishEndpoint.method, method: this.props.sectionConfig.publishEndpoint.method,
defaultData: { SecurityID: this.props.config.SecurityID },
payloadSchema: { payloadSchema: {
id: { urlReplacement: ':id', remove: true }, id: { urlReplacement: ':id', remove: true },
}, },
@ -27,7 +28,7 @@ class CampaignAdminContainer extends SilverStripeComponent {
} }
componentDidMount() { componentDidMount() {
window.ss.router(`/${this.props.config.campaignViewRoute}`, (ctx) => { window.ss.router(`/${this.props.sectionConfig.campaignViewRoute}`, (ctx) => {
this.props.actions.showCampaignView(ctx.params.id, ctx.params.view); this.props.actions.showCampaignView(ctx.params.id, ctx.params.view);
}); });
} }
@ -55,7 +56,7 @@ class CampaignAdminContainer extends SilverStripeComponent {
* @return object * @return object
*/ */
renderIndexView() { renderIndexView() {
const schemaUrl = this.props.config.forms.editForm.schemaUrl; const schemaUrl = this.props.sectionConfig.forms.editForm.schemaUrl;
return ( return (
<div className="cms-middle no-preview"> <div className="cms-middle no-preview">
@ -80,7 +81,7 @@ class CampaignAdminContainer extends SilverStripeComponent {
renderItemListView() { renderItemListView() {
const props = { const props = {
campaignId: this.props.campaignId, campaignId: this.props.campaignId,
itemListViewEndpoint: this.props.config.itemListViewEndpoint, itemListViewEndpoint: this.props.sectionConfig.itemListViewEndpoint,
publishApi: this.publishApi, publishApi: this.publishApi,
}; };
@ -105,7 +106,7 @@ class CampaignAdminContainer extends SilverStripeComponent {
* @return object - Instanciated React component * @return object - Instanciated React component
*/ */
createFn(Component, props) { createFn(Component, props) {
const campaignViewRoute = this.props.config.campaignViewRoute; const campaignViewRoute = this.props.sectionConfig.campaignViewRoute;
if (props.component === 'GridField') { if (props.component === 'GridField') {
const extendedProps = Object.assign({}, props, { const extendedProps = Object.assign({}, props, {
@ -149,19 +150,23 @@ class CampaignAdminContainer extends SilverStripeComponent {
} }
CampaignAdminContainer.propTypes = { CampaignAdminContainer.propTypes = {
config: React.PropTypes.shape({ sectionConfig: React.PropTypes.shape({
forms: React.PropTypes.shape({ forms: React.PropTypes.shape({
editForm: React.PropTypes.shape({ editForm: React.PropTypes.shape({
schemaUrl: React.PropTypes.string, schemaUrl: React.PropTypes.string,
}), }),
}), }),
}), }),
config: React.PropTypes.shape({
SecurityID: React.PropTypes.string,
}),
sectionConfigKey: React.PropTypes.string.isRequired, sectionConfigKey: React.PropTypes.string.isRequired,
}; };
function mapStateToProps(state, ownProps) { function mapStateToProps(state, ownProps) {
return { return {
config: state.config.sections[ownProps.sectionConfigKey], config: state.config,
sectionConfig: state.config.sections[ownProps.sectionConfigKey],
campaignId: state.campaign.campaignId, campaignId: state.campaign.campaignId,
view: state.campaign.view, view: state.campaign.view,
}; };