Class: ShopifyAPI::Session

Inherits:
Object
  • Object
show all
Defined in:
lib/shopify_api/session.rb

Overview

The Shopify API authenticates each call via HTTP Authentication, using

  * the application's API key as the username, and
  * a hex digest of the application's shared secret and an 
    authentication token as the password.

Generation & acquisition of the beforementioned looks like this:

  0. Developer (that's you) registers Application (and provides a
     callback url) and receives an API key and a shared secret

  1. User visits Application and are told they need to authenticate the
     application first for read/write permission to their data (needs to
     happen only once). User is asked for their shop url.

  2. Application redirects to Shopify : GET <user's shop url>/admin/api/auth?api_key=<API key>
     (See Session#create_permission_url)

  3. User logs-in to Shopify, approves application permission request

  4. Shopify redirects to the Application's callback url (provided during
     registration), including the shop's name, and an authentication token in the parameters:
       GET client.com/customers?shop=snake-oil.myshopify.com&t=a94a110d86d2452eb3e2af4cfb8a3828

  5. Authentication password computed using the shared secret and the
     authentication token (see Session#computed_password)

  6. Profit!
     (API calls can now authenticate through HTTP using the API key, and
     computed password)

LoginController and ShopifyLoginProtection use the Session class to set Shopify::Base.site
so that all API calls are authorized transparently and end up just looking like this:

  # get 3 products
  @products = ShopifyAPI::Product.find(:all, :params => {:limit => 3})

  # get latest 3 orders
  @orders = ShopifyAPI::Order.find(:all, :params => {:limit => 3, :order => "created_at DESC" })

As an example of what your LoginController should look like, take a look
at the following:

  class LoginController < ApplicationController
    def index
      # Ask user for their #{shop}.myshopify.com address
    end

    def authenticate
      redirect_to ShopifyAPI::Session.new(params[:shop]).create_permission_url
    end

    # Shopify redirects the logged-in user back to this action along with
    # the authorization token t.
    # 
    # This token is later combined with the developer's shared secret to form
    # the password used to call API methods.
    def finalize
      shopify_session = ShopifyAPI::Session.new(params[:shop], params[:t])
      if shopify_session.valid?
        session[:shopify] = shopify_session
        flash[:notice] = "Logged in to shopify store."

        return_address = session[:return_to] || '/home'
        session[:return_to] = nil
        redirect_to return_address
      else
        flash[:error] = "Could not log in to Shopify store."
        redirect_to :action => 'index'
      end
    end

    def logout
      session[:shopify] = nil
      flash[:notice] = "Successfully logged out."

      redirect_to :action => 'index'
    end
  end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url, token = nil, params = nil) ⇒ Session

Returns a new instance of Session.



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/shopify_api/session.rb', line 123

def initialize(url, token = nil, params = nil)
  self.url, self.token = url, token

  if params
    unless self.class.validate_signature(params) && params[:timestamp].to_i > 24.hours.ago.utc.to_i
      raise "Invalid Signature: Possible malicious login" 
    end
  end

  self.class.prepare_url(self.url)
end

Instance Attribute Details

#nameObject

Returns the value of attribute name.



88
89
90
# File 'lib/shopify_api/session.rb', line 88

def name
  @name
end

#tokenObject

Returns the value of attribute token.



88
89
90
# File 'lib/shopify_api/session.rb', line 88

def token
  @token
end

#urlObject

Returns the value of attribute url.



88
89
90
# File 'lib/shopify_api/session.rb', line 88

def url
  @url
end

Class Method Details

.prepare_url(url) ⇒ Object



108
109
110
111
112
# File 'lib/shopify_api/session.rb', line 108

def prepare_url(url)
  return nil if url.blank?
  url.gsub!(/https?:\/\//, '')                            # remove http:// or https://
  url.concat(".myshopify.com") unless url.include?('.')   # extend url to myshopify.com if no host is given
end

.setup(params) ⇒ Object



92
93
94
# File 'lib/shopify_api/session.rb', line 92

def setup(params)
  params.each { |k,value| send("#{k}=", value) }
end

.temp(domain, token, &block) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/shopify_api/session.rb', line 96

def temp(domain, token, &block)
  session = new(domain, token)

  original_site = ShopifyAPI::Base.site
  begin
    ShopifyAPI::Base.site = session.site
    yield
  ensure
    ShopifyAPI::Base.site = original_site
  end
end

.validate_signature(params) ⇒ Object



114
115
116
117
118
119
# File 'lib/shopify_api/session.rb', line 114

def validate_signature(params)
  return false unless signature = params[:signature]

  sorted_params = params.except(:signature, :action, :controller).collect{|k,v|"#{k}=#{v}"}.sort.join
  Digest::MD5.hexdigest(secret + sorted_params) == signature
end

Instance Method Details

#create_permission_urlObject



139
140
141
142
# File 'lib/shopify_api/session.rb', line 139

def create_permission_url
  return nil if url.blank? || api_key.blank?
  "http://#{url}/admin/api/auth?api_key=#{api_key}"
end

#shopObject



135
136
137
# File 'lib/shopify_api/session.rb', line 135

def shop
  Shop.current
end

#siteObject

Used by ActiveResource::Base to make all non-authentication API calls

(ShopifyAPI::Base.site set in ShopifyLoginProtection#shopify_session)



147
148
149
# File 'lib/shopify_api/session.rb', line 147

def site
  "#{protocol}://#{api_key}:#{computed_password}@#{url}/admin"
end

#valid?Boolean

Returns:

  • (Boolean)


151
152
153
# File 'lib/shopify_api/session.rb', line 151

def valid?
  url.present? && token.present?
end