class Netfira::WebConnect::RackApp::Action

Constants

VALID_BASE64

Attributes

env[R]
headers[R]
input[R]
path[R]
query_string[R]
shop[R]
timeout[R]
verb[R]

Public Class Methods

action_classes() click to toggle source
# File lib/netfira/web_connect/rack_app/action.rb, line 9
def self.action_classes
  @action_classes ||= find_action_classes
end
create(action, version = nil) click to toggle source
# File lib/netfira/web_connect/rack_app/action.rb, line 17
def self.create(action, version = nil)
  version ||= latest_version
  klass = nil
  until klass or version < 1
    klass = (action_classes[version] || {})[action] # todo needs explination or to be rewritten to be easily readable
    version -= 1
  end
  klass and klass.new
end
latest_version() click to toggle source
# File lib/netfira/web_connect/rack_app/action.rb, line 13
def self.latest_version
  @latest_version ||= action_classes.keys.max
end
new() click to toggle source
# File lib/netfira/web_connect/rack_app/action.rb, line 27
def initialize
  @headers = ActiveSupport::HashWithIndifferentAccess.new
end

Private Class Methods

find_action_classes() click to toggle source
# File lib/netfira/web_connect/rack_app/action.rb, line 37
def self.find_action_classes
  action_classes = {}

  # Loop through all constants in this class's namespace
  constants.each do |constant|

    # Match Version# constants (modules containing actions)
    if constant =~ /\AVersion(\d+)\z/

      # The module containing the actions
      mod = const_get(constant)

      # A hash of actions, e.g. :commit => Action::Version8::Commit
      action_classes[$1.to_i] = Hash[mod.constants.map do |name|
        mod.const_get(name)
      end.select do |klass|
        klass < self
      end.map do |klass|
        [klass.name.demodulize.underscore.to_sym, klass]
      end]
    end
  end

  action_classes
end

Public Instance Methods

allow(*args) click to toggle source

This method restricts request verbs (symbols) and input types (classes)

# File lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb, line 22
def allow(*args)

  # Restrict verbs
  verbs = args.select { |x| Symbol === x }
  unless verbs.empty? || verbs.include?(verb)
    raise MethodNotAllowed.new("The #{verb.to_s.upcase} verb is not allowed", allowed: verbs.map{ |v| v.to_s.upcase })
  end

  # Restrict input type
  types = args.select { |x| Class === x }
  unless types.empty? || types.find { |t| t === input }
    raise BadRequest.new('Unexpected request body type', allowed: types.map { |t| t.name })
  end
end
class_for_record_type(type) click to toggle source
# File lib/netfira/web_connect/rack_app/action_helpers/data_types.rb, line 4
def class_for_record_type(type)
  class_for_type type, Model::Record
end
class_for_relation_type(type) click to toggle source
# File lib/netfira/web_connect/rack_app/action_helpers/data_types.rb, line 8
def class_for_relation_type(type)
  class_for_type type, Model::Relation
end
dispatch_event(*args, &block) click to toggle source
# File lib/netfira/web_connect/rack_app/action.rb, line 31
def dispatch_event(*args, &block)
  Netfira::WebConnect.dispatch_event *args, &block
end
header(name, value) click to toggle source

This method sets a response header, e.g.header 'Content-Type', 'text/plain'

# File lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb, line 7
def header(name, value)
  name = name.to_s.split(/[- _]/).map(&:capitalize).join('-').to_sym
  if value
    headers[name] = value
  else
    headers.delete name
  end
end
import_env(env) click to toggle source
# File lib/netfira/web_connect/rack_app/action_helpers/env_importer.rb, line 7
def import_env(env)
  @env = env
  @shop = nil
  session = nil

  # Parse the environment
  request = Rack::Request.new env

  # Restore sessions
  session_lifetime = Netfira::WebConnect.session_lifetime
  session_token = session_lifetime && env['HTTP_X_SESSION']
  if session_token
    session = Models::Session.by_token(session_token)
    if session
      @shop = session.shop
    else
      raise Unauthorized, 'Session has expired'
    end
  end

  # Authentication
  unless @shop
    authenticator = Netfira::WebConnect.authenticator
    if authenticator.respond_to? :call
      shop_name = env['HTTP_X_SHOP_NAME']
      password = request['pw'] || env['HTTP_X_PASSWORD']

      # Basic auth
      auth = Rack::Auth::Basic::Request.new(env)
      if auth.provided? && auth.basic?
        shop_name ||= auth.username
        password ||= auth.credentials[1]
      end

      # Make shop_name a copy, so the authenticator can't mutate
      # the original string
      shop_name = shop_name.dup if shop_name

      result = authenticator.call shop_name, password
      raise Unauthorized unless result
      @shop = Netfira::WebConnect::Models::Shop.find_or_create_by(name: shop_name)

      header :x_vary_password, result if String === result

      # Sessions
      if session_lifetime
        session ||= @shop.sessions.new
        session.expires_at = Time.now + session_lifetime if Fixnum === session_lifetime
        session.save
        header :x_session, session.token
      end

    elsif authenticator.nil?
      @shop = Netfira::WebConnect.anonymous_shop
    else
      raise 'Authenticator is not callable'
    end
  end

  # The request verb (PUT, GET, POST etc)
  @verb = request.request_method.downcase.to_sym

  # Query string
  @query_string = request.GET

  # The X-Timeout header
  timeout = env['HTTP_X_TIMEOUT']
  @timeout = timeout.to_i if timeout

  # Path components
  if env['PATH_INFO'] =~ /\A\/\d+\/[^\/]+\/(.+)\z/
    @path = $1.split('/').map{ |x| Rack::Utils.unescape x }
  end

  # Input
  if put? or post?
    @input = request.body

    # Decode base64
    if (env['HTTP_CONTENT_ENCODING'] || env['CONTENT_ENCODING'] || '').downcase == 'base64'
      input = @input.read.gsub(/\s+/, '')
      raise BadRequest, 'Invalid base64 in request body' unless input.length % 4 == 0 && input =~ VALID_BASE64
      @input = StringIO.new(input.unpack('m').first)
    end

    # Unserialize JSON
    begin
      @input = JSON.parse @input.read.sub(/\A\z/, 'null'), quirks_mode: true if request.media_type == 'application/json'
    rescue JSON::ParserError => error
      raise BadRequest.new('Invalid JSON in request body', details: error.message.sub(/^\d+:\s*/, ''))
    end
  end

end
send_file(path) click to toggle source
# File lib/netfira/web_connect/rack_app/action_helpers/send_file.rb, line 6
def send_file(path)
  content = nil
  if path.to_s.end_with? '.erb'
    template = ERB.new(path.read)
    content = template.result(binding)
  end
  raise RackApp::Exceptions::SendFile.new(path, content)
end

Private Instance Methods

class_for_type(type, superclass) click to toggle source
# File lib/netfira/web_connect/rack_app/action_helpers/data_types.rb, line 14
def class_for_type(type, superclass)
  raise BadRequest, 'No data type specified' if type.nil?
  klass = Models.const_defined?(type) && Models.const_get(type)
  raise BadRequest, 'Invalid or unknown data type' unless klass && klass < superclass
  klass
end