Class: Netfira::WebConnect::RackApp::Action

Inherits:
Object
  • Object
show all
Includes:
Exceptions::HttpExceptions
Defined in:
lib/netfira/web_connect/rack_app/action.rb,
lib/netfira/web_connect/rack_app/action_helpers/send_file.rb,
lib/netfira/web_connect/rack_app/action_helpers/data_types.rb,
lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb,
lib/netfira/web_connect/rack_app/action_helpers/env_importer.rb

Defined Under Namespace

Modules: Version1, Version8

Constant Summary collapse

VALID_BASE64 =
%r`^([a-z\d+/]{4})*([a-z\d+/]{4}|[a-z\d+/]{3}=|[a-z\d+/]{2}==)$`i

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAction

Returns a new instance of Action.



27
28
29
# File 'lib/netfira/web_connect/rack_app/action.rb', line 27

def initialize
  @headers = ActiveSupport::HashWithIndifferentAccess.new
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env.



4
5
6
# File 'lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb', line 4

def env
  @env
end

#headersObject (readonly)

Returns the value of attribute headers.



4
5
6
# File 'lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb', line 4

def headers
  @headers
end

#inputObject (readonly)

Returns the value of attribute input.



4
5
6
# File 'lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb', line 4

def input
  @input
end

#pathObject (readonly)

Returns the value of attribute path.



4
5
6
# File 'lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb', line 4

def path
  @path
end

#query_stringObject (readonly)

Returns the value of attribute query_string.



4
5
6
# File 'lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb', line 4

def query_string
  @query_string
end

#shopObject (readonly)

Returns the value of attribute shop.



4
5
6
# File 'lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb', line 4

def shop
  @shop
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



4
5
6
# File 'lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb', line 4

def timeout
  @timeout
end

#verbObject (readonly)

Returns the value of attribute verb.



4
5
6
# File 'lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb', line 4

def verb
  @verb
end

Class Method Details

.action_classesObject



9
10
11
# 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) ⇒ Object



17
18
19
20
21
22
23
24
25
# 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_versionObject



13
14
15
# File 'lib/netfira/web_connect/rack_app/action.rb', line 13

def self.latest_version
  @latest_version ||= action_classes.keys.max
end

Instance Method Details

#allow(*args) ⇒ Object

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



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 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) ⇒ Object



4
5
6
# 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) ⇒ Object



8
9
10
# 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) ⇒ Object



31
32
33
# 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) ⇒ Object

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



7
8
9
10
11
12
13
14
# 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) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# 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) ⇒ Object



6
7
8
9
10
11
12
13
# 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