Overview

Proper application design, intelligent programming, and secure infrastructure are all essential in creating a secure e-commerce store using any software (Spree included.) The Spree team has done its best to provide you with the tools to create a secure and profitable web presence but it is up to you to take these tools and put them in good practice. We highly recommend reading and understanding the Rails Security Guide

Reporting Security Issues

Please do not announce potential security vulnerabilities in public. We have a dedicated email address. We will work quickly to determine the severity of the issue and provide a fix for the appropriate versions. We will credit you with the discovery of this patch by naming you in a blog post.

If you would like to provide a patch yourself for the security issue do not open a pull request for it. Instead, create a commit on your fork of Spree and run this command:

$ git format-patch HEAD~1..HEAD --stdout > patch.txt

This command will generate a file called patch.txt with your changes. Please email a description of the patch along with the patch itself to our dedicated email address.

Authentication

If you install spree_auth_devise when setting up your app, we use a third party authentication library for Ruby known as Devise. This library provides a host of useful functionality that is in turn available to Spree, including the following features:

  • Authentication
  • Strong password encryption (with the ability to specify your own algorithms)
  • “Remember Me” cookies
  • “Forgot my password” emails
  • Token-based access (for REST API)

Devise Configuration

A default Spree install comes with the spree_auth_devise gem, which provides authentication for Spree using Devise. This section of the guide covers the default setup. If you’re using your own authentication, please consult the manual for that authentication engine.

We have configured Devise to handle only what is needed to authenticate with a Spree site. The following details cover the default configurations:

  • Passwords are stored in the database encrypted with the salt.
  • User authentication is done through the database query.
  • User registration is enabled and the user’s login is available immediately (no validation emails).
  • There is a remember me and password recovery tool built in and enabled through Devise.

These configurations represent a reasonable starting point for a typical e-commerce site. Devise can be configured extensively to allow for a different feature set but that is currently beyond the scope of this document. Developers are encouraged to visit the Devise wiki for more details.

REST API

The REST API behaves slightly differently than a standard user. First, an admin has to create the access key before any user can query the REST API. This includes generating the key for the admin him/herself. This is not the case if Spree::Api::Config[:requires_authentication] is set to false.

In cases where Spree::Api::Config[:requires_authentication] is set to false`, read-only requests in the API will be possible for all users. For actions that modify data within Spree, a user will need to have an API key and then their user record would need to have permission to perform those actions.

It is up to you to communicate that key. As an added measure, this authentication has to occur on every request made through the REST API as no session or cookies are created or stored for the REST API.

Authorization

Spree uses the excellent CanCan gem to provide authorization services. If you are unfamiliar with it, you should take a look at Ryan Bates’ excellent screencast on the topic (or read the transcribed version). A detailed explanation of CanCan is beyond the scope of this guide.

Default Rules

The follow Spree source code is taken from ability.rb and provides some insight into the default authorization rules:

if user.respond_to?(:has_spree_role?) && user.has_spree_role?('admin')
  can :manage, :all
else
  #############################
  can [:read,:update,:destroy], Spree.user_class, :id => user.id
  can :create, Spree.user_class
  #############################
  can :read, Order do |order, token|
    order.user == user || order.token && token == order.token
  end
  can :update, Order do |order, token|
    order.user == user || order.token && token == order.token
  end
  can :create, Order

  can :read, Address do |address|
    address.user == user
  end

  #############################
  can :read, Product
  can :index, Product
  #############################
  can :read, Taxon
  can :index, Taxon
  #############################
end

The above rule set has the following practical effects for Spree users

  • Admin role can access anything (the rest of the rules are ignored)
  • Anyone can create a User, only the user associated with an account can perform read or update operations for that user.
  • Anyone can create an Order, only the user associated with the order can perform read or update operations.
  • Anyone can read product pages and look at lists of Products (including search operations).
  • Anyone can read or view a list of Taxons.

Enforcing the Rules

CanCan is only effective in enforcing authorization rules if it’s asked. In other words, if the source code does not check permissions there is no way to deny access based on those permissions. This is generally handled by adding the appropriate code to your Rails controllers. For more information please see the CanCan Wiki.

Custom Authorization Rules

We have modified the original CanCan concept to make it easier for extension developers and end users to add their own custom authorization rules. For instance, if you have an “artwork extension” that allows users to attach custom artwork to an order, you will need to add rules so that they have permissions to do so.

The trick to adding custom authorization rules is to add an AbilityDecorator to your extension and then to register these abilities. The following code is an example of how to restrict access so that only the owner of the artwork can update it or view it.

class AbilityDecorator
  include CanCan::Ability
  def initialize(user)
    can :read, Artwork do |artwork|
      artwork.order && artwork.order.user == user
    end
    can :update, Artwork do |artwork|
      artwork.order && artwork.order.user == user
    end
  end
end

Spree::Ability.register_ability(AbilityDecorator)

Custom Roles in the Admin Namespace

If you plan on allowing a custom role you create to access the Spree administrative panels, there are a couple of considerations to keep in mind.

Spree authorizes all of its administrative panels with two CanCan authorization commands: :admin and the name of the action being authorized. If you want a custom role to be able to access a particular admin panel, you have to specify that your role can access both :admin and the name of the action on the relevant resource. For example, if you want your Sales Representatives to be able to access the Admin Orders panel without giving them access to anything else in the Admin namespace, you would have to specify the following in an AbilityDecorator:

class AbilityDecorator
  include CanCan::Ability
  def initialize(user)
    if user.respond_to?(:has_spree_role?) && user.has_spree_role?('sales_rep')
      can [:admin, :index, :show], Spree::Order
    end
  end
end

Spree::Ability.register_ability(AbilityDecorator)

This is required by the following code in Spree’s Admin::BaseController which is the controller every controller in the Admin namespace inherits from.

def authorize_admin
  if respond_to?(:model_class, true) && model_class
    record = model_class
  else
    record = Object
  end
  authorize! :admin, record
  authorize! action, record
end

If you need to create custom controllers for your own models under the Admin namespace, you will need to manually specify the model your controller manipulates by defining a model_class method in that controller.

module Spree
  module Admin
    class WidgetsController < BaseController
      def index
        # Relevant code in here
      end

    private
      def model_class
        Widget
      end
    end
  end
end

This is necessary because CanCan cannot, by default, detect the model used to authorize controllers under the Admin namespace. By specifying model_class, Spree knows what to tell CanCan to use to authorize your controller.

Tokenized Permissions

There are situations where it may be desirable to restrict access to a particular resource without requiring a user to authenticate in order to have that access. Spree allows so-called “guest checkouts” where users just supply an email address and they’re not required to create an account. In these cases you still want to restrict access to that order so only the original customer can see it. The solution is to use a “tokenized” URL.

http://example.com/orders?token=aidik313dsfs49d

Spree provides a TokenizedPermission model used to grant access to various resources through a secure token. This model works in conjunction with the Spree::TokenResource module which can be used to add tokenized access functionality to any Spree resource.

module Spree
  module Core
    module TokenResource
      module ClassMethods
        def token_resource
          has_one :tokenized_permission, :as => :permissable
          delegate :token, :to => :tokenized_permission, :allow_nil => true
          after_create :create_token
        end
      end

      def create_token
        permission = build_tokenized_permission
        permission.token = token = ::SecureRandom::hex(8)
        permission.save!
        token
      end

      def self.included(receiver)
        receiver.extend ClassMethods
      end
    end
  end
end

ActiveRecord::Base.class_eval { include Spree::Core::TokenResource }

The Order model is one such model in Spree where this interface is already in use. The following code snippet shows how to add this functionality through the use of the token_resource declaration:

Spree::Order.class_eval do
  token_resource
end

If we examine the default CanCan permissions for Order we can see how tokens can be used to grant access in cases where the user is not authenticated.

can :read, Spree::Order do |order, token|
  order.user == user || order.token && token == order.token
end

can :update, Spree::Order do |order, token|
  order.user == user || order.token && token == order.token
end

can :create, Spree::Order

This configuration states that in order to read or update an order, you must be either authenticated as the correct user, or supply the correct authorizing token.

The final step is to ensure that the token is passed to CanCan when the authorization is performed, which is done in the controller.

authorize! action, resource, session[:access_token]

Of course this also assumes that the token has been stored in the session. Generally this can be achieved with a route that maps the token to the correct parameter:

get '/orders/:id/token/:token' => 'orders#show', :as => :token_order

This is followed by a call to store the token in the session for possible future access.

  session[:access_token] ||= params[:token]

Credit Card Data

PCI Compliance

All store owners wishing to process credit card transactions should be familiar with PCI Compliance. Spree makes absolutely no warranty regarding PCI compliance (or anything else for that matter - see the LICENSE for details.) We do, however, follow common sense security practices in handling credit card data.

Transmit Exactly Once

Spree uses extreme caution in its handling of credit cards. In production mode, credit card data is transmitted to Spree via SSL. The data is immediately relayed to your chosen payment gateway and then discarded. The credit card data is never stored in the database (not even temporarily) and it exists in memory on the server for only a fraction of a second before it is discarded.

Spree does store the last four digits of the credit card and the expiration month and date. You could easily customize Spree further if you wanted and opt out of storing even that little bit of information.

Payment Profiles

Spree also supports the use of “payment profiles.” This means that you can “store” a customer’s credit card information in your database securely. More precisely you store a “token” that allows you to use the credit card again. The credit card gateway is actually the place where the credit card is stored. Spree ends up storing a token that can be used to authorize new charges on that same card without having to store sensitive credit card details.

Spree has out of the box support for Authorize.net CIM payment profiles.

Other Options

There are also third-party extensions for Paypal’s Express Checkout (formerly called Paypal Express.) These types of checkout services handle processing of the credit card information offsite (the data never touches your server) and greatly simplify the requirements for PCI compliance.

Braintree also offers a very interesting gateway option that achieves a similar benefit to Express Checkout but allows the entire process to appear to be taking place on the site. In other words, the customer never appears to leave the store during the checkout. They describe this as a “transparent redirect.” The Braintree team is very interested in helping other Ruby developers use their gateway and have provided support to Spree developers in the past who were interested in using their product.

Security Alerts

Spree will periodically check for important security and release announcements. You will see them on the administration console pages. Alerts may be dismissed after you have read them. The automatic checking can be disabled under “Configuration” => “General Settings”, or by setting the Spree::Config[:check_for_alerts] setting to false. Some configuration information is included when checking so the alerts can be customized to your specific installation. Here is an example of the configuration information included in the alert request:

{
  "name": "Spree Demo Site",
  "rails_version": "3.1.1",
  "version": "0.70.1",
  "rails_env": "production",
  "host": "www.spreecommerce.com"
}