bows 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/ribbon/intercom.rb +42 -0
- data/lib/ribbon/intercom/client.rb +61 -0
- data/lib/ribbon/intercom/client/mock_sdk.rb +13 -0
- data/lib/ribbon/intercom/client/sdk.rb +99 -0
- data/lib/ribbon/intercom/client/sdk/adapters.rb +10 -0
- data/lib/ribbon/intercom/client/sdk/adapters/adapter.rb +77 -0
- data/lib/ribbon/intercom/client/sdk/adapters/adapter/response.rb +13 -0
- data/lib/ribbon/intercom/client/sdk/adapters/http_adapter.rb +32 -0
- data/lib/ribbon/intercom/client/sdk/adapters/http_adapter/connection.rb +34 -0
- data/lib/ribbon/intercom/client/sdk/adapters/local_adapter.rb +55 -0
- data/lib/ribbon/intercom/client/sdk/adapters/mock_adapter.rb +40 -0
- data/lib/ribbon/intercom/errors.rb +66 -0
- data/lib/ribbon/intercom/package.rb +121 -0
- data/lib/ribbon/intercom/packageable.rb +6 -0
- data/lib/ribbon/intercom/packageable/mixin.rb +29 -0
- data/lib/ribbon/intercom/packet.rb +52 -0
- data/lib/ribbon/intercom/packet/method_queue.rb +28 -0
- data/lib/ribbon/intercom/railtie.rb +14 -0
- data/lib/ribbon/intercom/service.rb +273 -0
- data/lib/ribbon/intercom/service/channel.rb +203 -0
- data/lib/ribbon/intercom/service/channel/stores.rb +9 -0
- data/lib/ribbon/intercom/service/channel/stores/mock_store.rb +40 -0
- data/lib/ribbon/intercom/service/channel/stores/redis_store.rb +196 -0
- data/lib/ribbon/intercom/service/channel/stores/store.rb +31 -0
- data/lib/ribbon/intercom/utils.rb +72 -0
- data/lib/ribbon/intercom/utils/method_chain.rb +38 -0
- data/lib/ribbon/intercom/utils/mixins.rb +5 -0
- data/lib/ribbon/intercom/utils/mixins/mock_safe.rb +26 -0
- data/lib/ribbon/intercom/utils/signer.rb +71 -0
- data/lib/ribbon/intercom/version.rb +5 -0
- data/lib/tasks/intercom.rake +24 -0
- metadata +215 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
module Client::SDK::Adapters
|
3
|
+
class MockAdapter < LocalAdapter
|
4
|
+
attr_reader :store
|
5
|
+
|
6
|
+
def connect(service)
|
7
|
+
if service.is_a?(Class) && service < Service
|
8
|
+
service = service.new(store: Service::Channel::Stores::MockStore.new)
|
9
|
+
end
|
10
|
+
|
11
|
+
unless service.is_a?(Service)
|
12
|
+
raise ArgumentError, "Expected a service, got: #{service.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
unless service.store.is_a?(Service::Channel::Stores::MockStore)
|
16
|
+
raise ArgumentError, "Expected service to have a MockStore, got: #{service.store.inspect}"
|
17
|
+
end
|
18
|
+
|
19
|
+
super(service)
|
20
|
+
@store = service.store
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_permissions(*perms, &block)
|
24
|
+
channel = store.open_channel(name: 'mock channel', may: perms)
|
25
|
+
secret = channel.rotate_secret!
|
26
|
+
with_channel(channel.token, secret, &block)
|
27
|
+
ensure
|
28
|
+
channel.close
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_channel(token, secret)
|
32
|
+
token_prv, secret_prv = self.channel_token, self.channel_secret
|
33
|
+
self.channel_token, self.channel_secret = token, secret
|
34
|
+
yield
|
35
|
+
ensure
|
36
|
+
self.channel_token, self.channel_secret = token_prv, secret_prv
|
37
|
+
end
|
38
|
+
end # MockAdapter
|
39
|
+
end # Client::SDK::Adapters
|
40
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
module Errors
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
##
|
6
|
+
# Packet errors
|
7
|
+
class PacketError < Error; end
|
8
|
+
class InvalidEncodingError < PacketError; end
|
9
|
+
|
10
|
+
#############
|
11
|
+
# Http Errors
|
12
|
+
class HttpError < Error; end
|
13
|
+
|
14
|
+
##
|
15
|
+
# Request Errors
|
16
|
+
|
17
|
+
# 400 Error
|
18
|
+
class RequestError < HttpError; end
|
19
|
+
class UnsafeArgumentError < RequestError; end
|
20
|
+
class InvalidSubjectSignatureError < RequestError; end
|
21
|
+
|
22
|
+
# 401 Error
|
23
|
+
class AuthenticationError < RequestError; end
|
24
|
+
|
25
|
+
# 403 Error
|
26
|
+
class ForbiddenError < RequestError; end
|
27
|
+
class InsufficientPermissionsError < ForbiddenError; end
|
28
|
+
|
29
|
+
# 404 Not Found
|
30
|
+
class NotFoundError < RequestError; end
|
31
|
+
class InvalidMethodError < NotFoundError; end
|
32
|
+
|
33
|
+
# 405 Method Not Allowed
|
34
|
+
class MethodNotAllowedError < RequestError; end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Server Errors
|
38
|
+
|
39
|
+
# 500 Error
|
40
|
+
class ServerError < HttpError; end
|
41
|
+
class UnsafeResponseError < ServerError; end
|
42
|
+
|
43
|
+
# General Errors
|
44
|
+
class UnsafeValueError < Error; end
|
45
|
+
|
46
|
+
# Intercom Errors
|
47
|
+
class ServiceNotDefinedError < Error; end
|
48
|
+
|
49
|
+
# Service Errors
|
50
|
+
class NoPermissionsError < Error; end
|
51
|
+
class MissingStoreError < Error; end
|
52
|
+
|
53
|
+
# Channel Store Errors
|
54
|
+
class ChannelStoreError < Error; end
|
55
|
+
class InvalidStoreParamsError < ChannelStoreError; end
|
56
|
+
class InvalidChannelError < ChannelStoreError; end
|
57
|
+
|
58
|
+
# SDK Errors
|
59
|
+
class RequestFailureError < Error; end
|
60
|
+
|
61
|
+
# Channel Errors
|
62
|
+
class ChannelNameMissingError < Error; end
|
63
|
+
class ChannelTokenMissingError < Error; end
|
64
|
+
class ChannelSecretMissingError < Error; end
|
65
|
+
end # Errors
|
66
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Ribbon::Intercom
|
4
|
+
class Package
|
5
|
+
include Utils::Mixins::MockSafe
|
6
|
+
|
7
|
+
class << self
|
8
|
+
##
|
9
|
+
# Package up any non-basic objects that include Packageable::Mixin.
|
10
|
+
def package(subject)
|
11
|
+
Utils.walk(subject) { |subject, context|
|
12
|
+
if Utils.basic_type?(subject)
|
13
|
+
subject
|
14
|
+
elsif context == :hash_key
|
15
|
+
# Hash keys must be basic types.
|
16
|
+
raise Errors::UnsafeResponseError, subject.inspect
|
17
|
+
elsif subject.is_a?(Packageable::Mixin)
|
18
|
+
_package_obj(subject, package(subject.package_data))
|
19
|
+
elsif subject.is_a?(Class) && subject < Packageable::Mixin
|
20
|
+
_package_obj(subject)
|
21
|
+
else
|
22
|
+
raise Errors::UnsafeResponseError, subject.inspect
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def _package_obj(subject, data=nil)
|
28
|
+
new(encode_subject(subject), data)
|
29
|
+
end
|
30
|
+
|
31
|
+
def encode_subject(subject)
|
32
|
+
Marshal.dump(subject)
|
33
|
+
end
|
34
|
+
|
35
|
+
def decode_subject(encoded_subject)
|
36
|
+
Marshal.load(encoded_subject)
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Walks the object and initializes all packages (i.e., sets them up to be
|
41
|
+
# accessed by the end-user on the client).
|
42
|
+
def init_packages(object, sdk)
|
43
|
+
Utils.walk(object) { |object|
|
44
|
+
object.send(:_init, sdk) if object.is_a?(Package)
|
45
|
+
object
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end # Class Methods
|
49
|
+
|
50
|
+
attr_reader :sdk
|
51
|
+
|
52
|
+
def initialize(subject_data=nil, data=nil)
|
53
|
+
@_subject_data = subject_data
|
54
|
+
@_data = data
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Begin a method chain (must be completed with `#end`).
|
59
|
+
#
|
60
|
+
# When `#end` is called on the method chain, the chain will be resolved
|
61
|
+
# remotely. This allows the chain of methods to be executed in one round-trip.
|
62
|
+
def begin
|
63
|
+
Utils::MethodChain.begin { |methods|
|
64
|
+
queue = Packet::MethodQueue.new
|
65
|
+
methods.each { |meth, *args| queue.enqueue(meth, *args) }
|
66
|
+
_send_method_queue(queue)
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def method_missing(meth, *args, &block)
|
73
|
+
if @_data && @_data.key?(meth)
|
74
|
+
@_data[meth]
|
75
|
+
elsif sdk
|
76
|
+
_call(meth, *args)
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
##
|
83
|
+
# Call a method on the subject remotely.
|
84
|
+
def _call(method_name, *args)
|
85
|
+
_send_method_queue(Packet::MethodQueue.new.enqueue(method_name, *args))
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Initializes the package on the client-side.
|
90
|
+
def _init(sdk)
|
91
|
+
@sdk = sdk
|
92
|
+
self.class.init_packages(@_data, sdk)
|
93
|
+
mock_safe! if sdk.mock_safe?
|
94
|
+
end
|
95
|
+
|
96
|
+
def _send_method_queue(method_queue)
|
97
|
+
_process_response(
|
98
|
+
sdk.send_packet(
|
99
|
+
subject: @_subject_data,
|
100
|
+
method_queue: method_queue
|
101
|
+
)
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
def marshal_dump
|
106
|
+
[@_subject_data, @_data]
|
107
|
+
end
|
108
|
+
|
109
|
+
def marshal_load(array)
|
110
|
+
@_subject_data = array[0]
|
111
|
+
@_data = array[1]
|
112
|
+
end
|
113
|
+
|
114
|
+
def _process_response(packet)
|
115
|
+
@_subject_data = packet.subject
|
116
|
+
@_data = self.class.init_packages(packet.package_data, sdk)
|
117
|
+
|
118
|
+
packet.retval
|
119
|
+
end
|
120
|
+
end # Package
|
121
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Ribbon::Intercom
|
4
|
+
class Packageable
|
5
|
+
module Mixin
|
6
|
+
class << self
|
7
|
+
def included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def package_with(*args)
|
14
|
+
args.map { |m| _package_with_methods << m.to_sym }
|
15
|
+
end
|
16
|
+
|
17
|
+
def _package_with_methods
|
18
|
+
@__package_with_methods ||= [].to_set
|
19
|
+
end
|
20
|
+
end # ClassMethods
|
21
|
+
|
22
|
+
##
|
23
|
+
# Returns the package data for the instance as a hash.
|
24
|
+
def package_data
|
25
|
+
self.class._package_with_methods.map { |meth| [meth, public_send(meth)] }.to_h
|
26
|
+
end
|
27
|
+
end # Mixin
|
28
|
+
end # Service::Subject
|
29
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Ribbon::Intercom
|
5
|
+
##
|
6
|
+
# Represents a collection of data to be passed between the service and client.
|
7
|
+
class Packet < OpenStruct
|
8
|
+
autoload(:MethodQueue, 'ribbon/intercom/packet/method_queue')
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def decode(encoded_packet)
|
12
|
+
hash = Marshal.load(Base64.strict_decode64(encoded_packet))
|
13
|
+
raise Errors::InvalidEncodingError, hash.inspect unless hash.is_a?(Hash)
|
14
|
+
new(hash)
|
15
|
+
end
|
16
|
+
end # Class Methods
|
17
|
+
|
18
|
+
def initialize(params={})
|
19
|
+
error = params.delete(:error)
|
20
|
+
super(params)
|
21
|
+
self.error = error if error
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Encode (marshal) the error before saving it. This allows the error to be
|
26
|
+
# decoded on the client when requested, rather than decoded at the same time
|
27
|
+
# the packet is decoded, which could cause problems if the error class doesn't
|
28
|
+
# exist on the client.
|
29
|
+
def error=(err)
|
30
|
+
self._encoded_error = Marshal.dump(err)
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Decode the error, which may not exist on the client.
|
35
|
+
# If the error class doesn't exist on the client, raise a ServerError.
|
36
|
+
def error
|
37
|
+
error? && Marshal.load(_encoded_error)
|
38
|
+
rescue
|
39
|
+
Errors::ServerError.new('unknown server error')
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Whether the packet contains an error
|
44
|
+
def error?
|
45
|
+
!!_encoded_error
|
46
|
+
end
|
47
|
+
|
48
|
+
def encode
|
49
|
+
Base64.strict_encode64(Marshal.dump(to_h))
|
50
|
+
end
|
51
|
+
end # Packet
|
52
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ribbon::Intercom
|
2
|
+
class Packet
|
3
|
+
class MethodQueue
|
4
|
+
##
|
5
|
+
# Enqueue a method with it's arguments.
|
6
|
+
# Supports method chaining.
|
7
|
+
def enqueue(name, *args)
|
8
|
+
self.tap { _queue << [name, *Utils.sanitize(args)] }
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Iterate through the methods and arguments.
|
13
|
+
def each(&block)
|
14
|
+
_queue.each(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
_queue.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def _queue
|
24
|
+
@__queue ||= []
|
25
|
+
end
|
26
|
+
end # MethodQueue
|
27
|
+
end # Packet
|
28
|
+
end # Ribbon::Intercom
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'yaml'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Ribbon::Intercom
|
6
|
+
class Service
|
7
|
+
autoload(:Channel, 'ribbon/intercom/service/channel')
|
8
|
+
|
9
|
+
# Used to signify an empty body
|
10
|
+
class EmptyResponse; end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def instance
|
14
|
+
@instance ||= new(store: _load_store)
|
15
|
+
end
|
16
|
+
|
17
|
+
def mock
|
18
|
+
Client::MockSDK.new(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def store(store_name, params={})
|
22
|
+
@_store_name = store_name
|
23
|
+
@_store_params = params
|
24
|
+
end
|
25
|
+
|
26
|
+
# The call method is needed here because Rails checks to see if a mounted
|
27
|
+
# Rack app can respond_to?(:call). Without it, the Service will not mount
|
28
|
+
def call(env)
|
29
|
+
instance.call(env)
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(meth, *args, &block)
|
33
|
+
instance.public_send(meth, *args, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def _load_store
|
37
|
+
raise "Store name missing" unless (store_name = @_store_name.to_s)
|
38
|
+
|
39
|
+
store = Utils.classify(store_name) + "Store"
|
40
|
+
Intercom::Service::Channel::Stores.const_get(store).new(@_store_params)
|
41
|
+
end
|
42
|
+
end # Class methods
|
43
|
+
|
44
|
+
attr_reader :request
|
45
|
+
attr_reader :channel
|
46
|
+
attr_reader :subject
|
47
|
+
attr_reader :request_packet
|
48
|
+
attr_reader :env
|
49
|
+
|
50
|
+
def initialize(opts={})
|
51
|
+
@_opts = opts.dup
|
52
|
+
end
|
53
|
+
|
54
|
+
def store
|
55
|
+
@store ||= @_opts[:store] or raise Errors::MissingStoreError
|
56
|
+
end
|
57
|
+
|
58
|
+
def open_channel(params={})
|
59
|
+
# Accept either an array of permissions or a string
|
60
|
+
store.open_channel(params).tap { |channel|
|
61
|
+
channel.may(Utils.method_identifier(self, :rotate_secret))
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def lookup_channel(token)
|
66
|
+
store.lookup_channel(token)
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Check that the channel has sufficient permissions to call the method.
|
71
|
+
#
|
72
|
+
# The `send` method is forbidden because it breaks the encapsulation guaranteed
|
73
|
+
# by intercom (i.e., private methods can't be called).
|
74
|
+
#
|
75
|
+
# In addition to the permissions granted to the channel, all channels have
|
76
|
+
# implicit permission to call public methods on basic types.
|
77
|
+
def sufficient_permissions?(base, intercom_method)
|
78
|
+
intercom_method != :send && (
|
79
|
+
Utils.basic_type?(base) ||
|
80
|
+
channel.may?(Utils.method_identifier(base, intercom_method))
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def call(env)
|
85
|
+
dup.call!(env)
|
86
|
+
end
|
87
|
+
|
88
|
+
def call!(env)
|
89
|
+
@env = env
|
90
|
+
|
91
|
+
response = catch(:response) {
|
92
|
+
begin
|
93
|
+
_process_request
|
94
|
+
rescue Exception => error
|
95
|
+
_respond_with_error!(error)
|
96
|
+
end
|
97
|
+
}
|
98
|
+
|
99
|
+
response.finish
|
100
|
+
end
|
101
|
+
|
102
|
+
def rotate_secret
|
103
|
+
channel.rotate_secret!
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def _process_request
|
109
|
+
_init_request
|
110
|
+
_authenticate_request!
|
111
|
+
_load_request_packet
|
112
|
+
_load_subject
|
113
|
+
response_packet = _process_methods
|
114
|
+
_respond_with_packet(response_packet)
|
115
|
+
end
|
116
|
+
|
117
|
+
def _init_request
|
118
|
+
@request = Rack::Request.new(env)
|
119
|
+
|
120
|
+
unless request.put?
|
121
|
+
_error!(Errors::MethodNotAllowedError, 'only PUT allowed')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def _authenticate_request!
|
126
|
+
unless _request_authenticated?
|
127
|
+
_error!(Errors::AuthenticationError, "invalid channel credentials")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def _load_request_packet
|
132
|
+
@request_packet = Packet.decode(request.body.read)
|
133
|
+
end
|
134
|
+
|
135
|
+
def _load_subject
|
136
|
+
if (encoded_subject=request_packet.subject) && !encoded_subject.empty?
|
137
|
+
@subject = Package.decode_subject(encoded_subject)
|
138
|
+
_error!(Errors::InvalidSubjectSignatureError) unless @subject
|
139
|
+
else
|
140
|
+
@subject = self
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Calls requested methods and returns a response packet for the client.
|
146
|
+
def _process_methods
|
147
|
+
retval = _call_methods
|
148
|
+
_prepare_response_packet(retval)
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Call all the methods in the method queue.
|
153
|
+
def _call_methods
|
154
|
+
method_queue = _load_method_queue
|
155
|
+
|
156
|
+
intercom_method = nil
|
157
|
+
base = subject
|
158
|
+
method_queue.each { |meth, *args|
|
159
|
+
intercom_method = meth
|
160
|
+
_sufficient_permissions!(base, meth)
|
161
|
+
base = base.public_send(meth, *args)
|
162
|
+
}
|
163
|
+
|
164
|
+
Package.package(base)
|
165
|
+
rescue NoMethodError => error
|
166
|
+
if error.name == intercom_method
|
167
|
+
_error!(Errors::InvalidMethodError, intercom_method)
|
168
|
+
else
|
169
|
+
raise
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def _load_method_queue
|
174
|
+
request_packet.method_queue.tap { |mq|
|
175
|
+
raise "No method queue given" unless mq
|
176
|
+
raise "Expected MethodQueue, got: #{mq.inspect}" unless mq.is_a?(Packet::MethodQueue)
|
177
|
+
raise "Empty MethodQueue" if mq.empty?
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Creates a successful response Packet to be returned to the client.
|
183
|
+
def _prepare_response_packet(retval)
|
184
|
+
Packet.new.tap { |packet|
|
185
|
+
unless self == subject # Order matters here! See: issue#52
|
186
|
+
# Need to send subject back in case it was modified by the methods.
|
187
|
+
packet.subject = Package.encode_subject(subject)
|
188
|
+
|
189
|
+
if subject.is_a?(Packageable::Mixin)
|
190
|
+
# Need to send the package data back in case it changed, too.
|
191
|
+
packet.package_data = Package.package(subject.package_data)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
packet.retval = retval
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
def _respond_with_packet(packet, status=200)
|
200
|
+
_respond!(status, {}, packet.encode)
|
201
|
+
end
|
202
|
+
|
203
|
+
def _request_authenticated?
|
204
|
+
auth = Rack::Auth::Basic::Request.new(env)
|
205
|
+
|
206
|
+
if auth.provided? && auth.basic?
|
207
|
+
token = auth.credentials[0]
|
208
|
+
secret = auth.credentials[1]
|
209
|
+
|
210
|
+
# Check if the request is authenticated
|
211
|
+
@channel = lookup_channel(token)
|
212
|
+
channel && channel.valid_secret?(secret)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def _sufficient_permissions!(base, intercom_method)
|
217
|
+
unless sufficient_permissions?(base, intercom_method)
|
218
|
+
required = Utils.method_identifier(base, intercom_method)
|
219
|
+
_error!(Errors::InsufficientPermissionsError, required)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def _response(status, headers={}, body=EmptyResponse)
|
224
|
+
body = body == EmptyResponse ? [] : [body]
|
225
|
+
headers = headers.merge("Content-Type" => "text/plain", "Transfer-Encoding" => "gzip")
|
226
|
+
Rack::Response.new(body, status, headers)
|
227
|
+
end
|
228
|
+
|
229
|
+
def _respond!(status, headers={}, body=EmptyResponse)
|
230
|
+
throw :response, _response(status, headers, body)
|
231
|
+
end
|
232
|
+
|
233
|
+
def _respond_with_error!(error, status=500)
|
234
|
+
_respond_with_packet(Packet.new(error: error), status)
|
235
|
+
end
|
236
|
+
|
237
|
+
def _error!(klass, message=nil)
|
238
|
+
error = message ? klass.new(message) : klass.new
|
239
|
+
_respond_with_error!(error, _error_to_http_code(error))
|
240
|
+
end
|
241
|
+
|
242
|
+
def _error_to_http_code(error)
|
243
|
+
case error
|
244
|
+
when Errors::MethodNotAllowedError
|
245
|
+
405
|
246
|
+
when Errors::NotFoundError
|
247
|
+
404
|
248
|
+
when Errors::ForbiddenError
|
249
|
+
403
|
250
|
+
when Errors::AuthenticationError
|
251
|
+
401
|
252
|
+
when Errors::RequestError
|
253
|
+
400
|
254
|
+
when Errors::ServerError
|
255
|
+
500
|
256
|
+
else
|
257
|
+
500
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
##
|
262
|
+
# Decodes the arguments.
|
263
|
+
#
|
264
|
+
# It's very important that this happens *after* channel authentication is
|
265
|
+
# performed. Since `args` comes from the client it could contain malicious
|
266
|
+
# marshalled data.
|
267
|
+
def _decode_args(args)
|
268
|
+
Utils.sanitize(Marshal.load(Base64.strict_decode64(args))).tap { |args|
|
269
|
+
raise Errors::UnsafeValueError unless args.is_a?(Array)
|
270
|
+
}
|
271
|
+
end
|
272
|
+
end # Service
|
273
|
+
end # Ribbon::Intercom
|