ribbon-intercom 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c3dd1e6bc419cb94651ef42356db8b7b8c6c48a6
4
+ data.tar.gz: 8c97e000d932acf6012513ab7339e506b0c6852a
5
+ SHA512:
6
+ metadata.gz: 7642b346c15bf955093543ebee843814cace7994bb3565b95db3e833ac13816d7b1f945abea4f8c4adcbe9e284349b7dab6f171271911e5ccf3990ccf8a2856e
7
+ data.tar.gz: 1bde67801438c049bf1877b2c78e8cfb44f25ca82f372370afd48ab0fbc67f7c6e87876e18d9bf1c7c810cacef2b7bb6220b021931d60392b0d74e77f56ef589
@@ -0,0 +1,25 @@
1
+ require 'ribbon/intercom/version'
2
+ require 'rack'
3
+
4
+ module Ribbon
5
+ module Intercom
6
+ require 'ribbon/intercom/railtie' if defined?(Rails)
7
+ autoload(:Service, 'ribbon/intercom/service')
8
+ autoload(:Errors, 'ribbon/intercom/errors')
9
+ autoload(:Client, 'ribbon/intercom/client')
10
+ autoload(:Utils, 'ribbon/intercom/utils')
11
+
12
+ module_function
13
+
14
+ def method_missing(meth, *args, &block)
15
+ client.send(meth, *args, &block)
16
+ end
17
+
18
+ def client
19
+ @__client ||= Client.new
20
+ end
21
+ end # Intercom
22
+ end # Ribbon
23
+
24
+ # Create a shortcut to the module
25
+ Intercom = Ribbon::Intercom
@@ -0,0 +1,31 @@
1
+ require 'ribbon/config'
2
+
3
+ module Ribbon::Intercom
4
+ class Client
5
+ autoload(:SDK, 'ribbon/intercom/client/sdk')
6
+
7
+ def config(&block)
8
+ (@__config ||= Ribbon::Config.new).tap { |config|
9
+ if block_given?
10
+ config.define(&block)
11
+ end
12
+ }
13
+ end
14
+
15
+ def [](handle)
16
+ _load_sdk(handle)
17
+ end
18
+
19
+ def _load_sdk(handle, url=nil, token=nil, secret=nil)
20
+ (@_sdk_instances ||= Hash.new { |hash, key|
21
+ if (remote = config.remote.select { |r| r.first == key }.last)
22
+ # Get the last remote if there are duplicates
23
+ config = remote.last
24
+ SDK.new(config[:url], config[:token], config[:secret])
25
+ else
26
+ raise Errors::RemoteNotFoundError
27
+ end
28
+ })[handle.to_sym]
29
+ end
30
+ end # Client
31
+ end # Ribbon::Intercom
@@ -0,0 +1,49 @@
1
+ module Ribbon::Intercom
2
+ class Client
3
+ class SDK
4
+ autoload(:Connection, 'ribbon/intercom/client/sdk/connection')
5
+ autoload(:Response, 'ribbon/intercom/client/sdk/response')
6
+
7
+ attr_reader :connection
8
+
9
+ def initialize(*args)
10
+ connect(*args)
11
+ end
12
+
13
+ def connect(*args)
14
+ @connection = Connection.new(*args)
15
+ end
16
+
17
+ def connected?
18
+ !!connection
19
+ end
20
+
21
+ def method_missing(meth, *args, &block)
22
+ _send_request(meth, *args)
23
+ end
24
+
25
+ private
26
+
27
+ def _send_request(method_name, *args)
28
+ response = connection.put("", method_name, args)
29
+
30
+ if response.success?
31
+ response.body
32
+ else
33
+ case response.code
34
+ when 401
35
+ raise Errors::AuthenticationError, request.body
36
+ when 403
37
+ raise Errors::MissingPermissionsError, response.missing_permissions.to_a.join(', ')
38
+ when 404
39
+ raise Errors::InvalidMethodError, "#{method_name} is not a valid method"
40
+ when 500
41
+ raise Errors::ServerError, response.body
42
+ else
43
+ raise Errors::RequestFailureError, response.body
44
+ end
45
+ end
46
+ end
47
+ end # Client
48
+ end # SDK
49
+ end # Ribbon::Intercom
@@ -0,0 +1,35 @@
1
+ require 'rest-client'
2
+
3
+ module Ribbon::Intercom
4
+ class Client
5
+ class SDK
6
+ class Connection
7
+ def initialize(url, token, secret)
8
+ @client = RestClient::Resource.new(
9
+ url,
10
+ user: token,
11
+ password: secret,
12
+ ) { |response, request, result| response }
13
+ end
14
+
15
+ def put(path, method_name, args)
16
+ path = _build_path(path)
17
+ headers = { "X-Intercom-Method" => method_name }
18
+ payload = _encode_args(args)
19
+
20
+ Response.new(@client[path].put(payload, headers))
21
+ end
22
+
23
+ private
24
+
25
+ def _build_path(path)
26
+ path[0] == '/' ? path : '/' + path
27
+ end
28
+
29
+ def _encode_args(args)
30
+ Base64.strict_encode64(Marshal.dump(Utils.sanitize(args)))
31
+ end
32
+ end # Connection
33
+ end # SDK
34
+ end # Client
35
+ end # Ribbon::Intercom
@@ -0,0 +1,59 @@
1
+ require 'base64'
2
+ require 'set'
3
+
4
+ module Ribbon::Intercom
5
+ class Client
6
+ class SDK
7
+ class Response
8
+ attr_reader :headers
9
+ attr_reader :body
10
+ attr_reader :code
11
+
12
+ def initialize(rest_client_response)
13
+ response = rest_client_response
14
+ @code = response.code
15
+ @headers = rest_client_response.headers
16
+ @body = _process_body(response.body)
17
+ _deep_freeze(@body)
18
+ end
19
+
20
+ def success?
21
+ code >= 200 && code < 300
22
+ end
23
+
24
+ def error?
25
+ !success?
26
+ end
27
+
28
+ def missing_permissions
29
+ @__missing_permissions ||= headers[:x_intercom_permissions_missing] &&
30
+ headers[:x_intercom_permissions_missing].split(',').to_set
31
+ end
32
+
33
+ def method_missing(meth, *args, &block)
34
+ if body.key?(meth.to_s)
35
+ body[meth.to_s]
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def _process_body(body)
44
+ Marshal.load(Base64.strict_decode64(body)) unless body.empty?
45
+ end
46
+
47
+ def _deep_freeze(obj)
48
+ if obj.respond_to?(:each)
49
+ obj.each do |elem|
50
+ _deep_freeze(elem)
51
+ end
52
+ end
53
+
54
+ obj.freeze
55
+ end
56
+ end # Response
57
+ end # SDK
58
+ end # Client
59
+ end # Ribbon::Intercom
@@ -0,0 +1,32 @@
1
+ module Ribbon::Intercom
2
+ module Errors
3
+ class Error < StandardError; end
4
+
5
+ # General Errors
6
+ class MissingPermissionsError < Error; end
7
+ class ServerError < Error; end
8
+ class UnsafeValueError < Error; end
9
+
10
+ # Intercom Errors
11
+ class RemoteNotFoundError < Error; end
12
+
13
+ # Service Errors
14
+ class NoPermissionsError < Error; end
15
+ class MissingStoreError < Error; end
16
+
17
+ # Channel Store Errors
18
+ class ChannelStoreError < Error; end
19
+ class InvalidStoreParamsError < ChannelStoreError; end
20
+ class InvalidChannelError < ChannelStoreError; end
21
+
22
+ # SDK Errors
23
+ class RequestFailureError < Error; end
24
+ class InvalidMethodError < Error; end
25
+ class AuthenticationError < Error; end
26
+
27
+ # Channel Errors
28
+ class ChannelNameMissingError < Error; end
29
+ class ChannelTokenMissingError < Error; end
30
+ class ChannelSecretMissingError < Error; end
31
+ end # Errors
32
+ end # Ribbon::Intercom
@@ -0,0 +1,16 @@
1
+ require 'ribbon/intercom'
2
+ require 'rails'
3
+
4
+ module Ribbon
5
+ module Intercom
6
+ class Railtie < Rails::Railtie
7
+ railtie_name :intercom
8
+
9
+ rake_tasks do
10
+ Dir[
11
+ File.expand_path("../../../tasks", __FILE__) + '/**/*.rake'
12
+ ].each { |rake_file| load rake_file }
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,185 @@
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
+ module ChannelStores
10
+ autoload(:Base, 'ribbon/intercom/service/channel_stores/base')
11
+ autoload(:RedisStore, 'ribbon/intercom/service/channel_stores/redis_store')
12
+ end
13
+
14
+ class << self
15
+ def instance
16
+ @instance ||= new(store: _load_store)
17
+ end
18
+
19
+ def default_store(store_name, params={})
20
+ @_store_name = store_name
21
+ @_store_params = params
22
+ end
23
+
24
+ def permissions(*perms)
25
+ @_next_permissions = perms.map(&:to_sym).to_set
26
+ end
27
+
28
+ def method_permissions
29
+ if (ancestor = ancestors.find { |a| a <= Service && a != self })
30
+ ancestor.method_permissions
31
+ else
32
+ {}
33
+ end.merge(@_method_permissions).dup
34
+ end
35
+
36
+ def method_missing(meth, *args, &block)
37
+ instance.send(meth, *args, &block)
38
+ end
39
+
40
+ def _load_store
41
+ raise "Store name missing" unless (store_name = @_store_name.to_s)
42
+
43
+ store = store_name.split("_").map(&:capitalize).join + "Store"
44
+ Intercom::Service::ChannelStores.const_get(store).new(@_store_params)
45
+ end
46
+ end # Class methods
47
+
48
+ attr_reader :request
49
+ attr_reader :channel
50
+ attr_reader :env
51
+
52
+ def initialize(opts={})
53
+ @_opts = opts.dup
54
+ end
55
+
56
+ def store
57
+ @store ||= @_opts[:store] or raise Errors::MissingStoreError
58
+ end
59
+
60
+ def open_channel(params={})
61
+ # Accept either an array of permissions or a string
62
+ store.open_channel(params)
63
+ end
64
+
65
+ def lookup_channel(token)
66
+ store.lookup_channel(token)
67
+ end
68
+
69
+ def method_permissions(method_name)
70
+ self.class.method_permissions[method_name.to_sym]
71
+ end
72
+
73
+ def method_permissions!(method_name)
74
+ method_permissions(method_name) or raise Errors::InvalidMethodError
75
+ end
76
+
77
+ def sufficient_permissions?(intercom_method)
78
+ permissions = method_permissions!(intercom_method)
79
+ channel.may?(*permissions)
80
+ end
81
+
82
+ def sufficient_permissions!(intercom_method)
83
+ sufficient_permissions?(intercom_method) or raise Errors::MissingPermissionsError
84
+ end
85
+
86
+ def process_request
87
+ @request = Rack::Request.new(env)
88
+ return _respond!(405) unless request.put?
89
+ return _respond!(401) unless _perform_basic_auth(request.env)
90
+
91
+ # Check permissions
92
+ intercom_method = env['HTTP_X_INTERCOM_METHOD'].to_sym
93
+ sufficient_permissions!(intercom_method)
94
+
95
+ # Execute the requested method
96
+ args = _decode_args(request.body.read)
97
+
98
+ [intercom_method, args]
99
+ rescue Errors::UnsafeValueError
100
+ _respond!(400, {}, "Arg types are invalid")
101
+ rescue Errors::MissingPermissionsError
102
+ _respond!(403, {"X-Intercom-Permissions-Missing" => _missing_permissions(intercom_method).to_a.join(',')}, "Permissions missing.")
103
+ rescue Errors::InvalidMethodError
104
+ _respond!(404, {}, "Method requested not found.")
105
+ end
106
+
107
+ def call_method(intercom_method, *args)
108
+ body = Utils.sanitize(send(intercom_method, *args))
109
+ _respond!(200, {}, body)
110
+ rescue Errors::UnsafeValueError
111
+ _respond!(500, {}, "Return value type is invalid")
112
+ end
113
+
114
+ def call(env)
115
+ dup.call!(env)
116
+ end
117
+
118
+ def call!(env)
119
+ @env = env
120
+
121
+ response = catch(:response) {
122
+ intercom_method, args = process_request
123
+ call_method(intercom_method, *args)
124
+ }
125
+
126
+ response.finish
127
+ rescue Exception => e
128
+ _response(500, {}, e.message).finish
129
+ end
130
+
131
+ private
132
+
133
+ def _perform_basic_auth(env)
134
+ auth = Rack::Auth::Basic::Request.new(env)
135
+
136
+ if auth.provided? && auth.basic?
137
+ token = auth.credentials[0]
138
+ secret = auth.credentials[1]
139
+
140
+ # Check if the request is authenticated
141
+ @channel = lookup_channel(token)
142
+ channel && channel.valid_secret?(secret)
143
+ end
144
+ end
145
+
146
+ def _missing_permissions(intercom_method)
147
+ method_permissions(intercom_method) - channel.permissions
148
+ end
149
+
150
+ def _response(status, headers={}, body="")
151
+ body = [_encode_body(body)]
152
+ headers = headers.merge("Content-Type" => "text/plain", "Transfer-Encoding" => "gzip")
153
+ Rack::Response.new(body, status, headers)
154
+ end
155
+
156
+ def _respond!(status, headers={}, body="")
157
+ throw :response, _response(status, headers, body)
158
+ end
159
+
160
+ def _encode_body(body)
161
+ Base64.strict_encode64(Marshal.dump(body))
162
+ end
163
+
164
+ def _decode_args(args)
165
+ Utils.sanitize(Marshal.load(Base64.strict_decode64(args))).tap { |args|
166
+ raise Errors::UnsafeValueError unless args.is_a?(Array)
167
+ }
168
+ end
169
+ end # Service
170
+
171
+ # Re-opening the class so the above instance methods don't get added to the
172
+ # permissions hash
173
+ class Service
174
+ class << self
175
+ def method_added(method_name)
176
+ (@_method_permissions ||= {})[method_name] = (@_next_permissions || [method_name].to_set).dup.freeze
177
+ @_next_permissions = nil
178
+ end
179
+ end # Class Methods
180
+
181
+ def rotate_secret
182
+ channel.rotate_secret!
183
+ end
184
+ end # Service
185
+ end # Ribbon::Intercom
@@ -0,0 +1,101 @@
1
+ require 'securerandom'
2
+ require 'bcrypt'
3
+
4
+ module Ribbon::Intercom
5
+ class Service
6
+ class Channel
7
+ include BCrypt
8
+
9
+ attr_reader :name
10
+ attr_reader :store
11
+ attr_reader :token
12
+ attr_reader :secret_hash_crt
13
+ attr_reader :secret_hash_prv
14
+
15
+ def initialize(store, params={})
16
+ @store = store
17
+ @name = params[:name] or raise Errors::ChannelNameMissingError
18
+ @token = params[:token]
19
+ @secret_hash_crt = params[:secret_hash_crt]
20
+ @secret_hash_prv = params[:secret_hash_prv]
21
+
22
+ may(*((params[:may] || []) | [:rotate_secret]))
23
+ end
24
+
25
+ def rotate_secret
26
+ SecureRandom.hex(16).tap { |secret|
27
+ @secret_hash_prv = secret_hash_crt
28
+ @secret_hash_crt = Password.create(secret)
29
+ }
30
+ end
31
+
32
+ def rotate_secret!
33
+ rotate_secret.tap { save }
34
+ end
35
+
36
+ def may(*args)
37
+ (@_allowed_to ||= Hash.new(false)).merge!(
38
+ Hash[
39
+ args.map { |perm| [perm.to_sym, true] }
40
+ ]
41
+ ).keys.to_set
42
+ end
43
+
44
+ def may!(*args)
45
+ may(*args).tap { save }
46
+ end
47
+
48
+ def may?(*args)
49
+ args.all? { |perm| @_allowed_to[perm.to_sym] }
50
+ end
51
+
52
+ def permissions
53
+ @_allowed_to.keys.to_set
54
+ end
55
+
56
+ def valid_secret?(secret)
57
+ !!secret && (secret_crt == secret || secret_prv == secret)
58
+ end
59
+
60
+ def secret_crt
61
+ @__secret_crt ||= _to_bcrypt_pw(secret_hash_crt)
62
+ end
63
+
64
+ def secret_prv
65
+ @__secret_prv ||= _to_bcrypt_pw(secret_hash_prv)
66
+ end
67
+
68
+ def save
69
+ # Loop until unique
70
+ unless token
71
+ loop { break unless store.token_exists?(@token = SecureRandom.hex(4)) }
72
+ end
73
+
74
+ _run_validations
75
+ store.persist(self)
76
+ end
77
+
78
+ def ==(other)
79
+ other.is_a?(Channel) &&
80
+ name == other.name &&
81
+ store == other.store &&
82
+ token == other.token &&
83
+ secret_crt.to_s == other.secret_crt.to_s &&
84
+ secret_prv.to_s == other.secret_prv.to_s &&
85
+ permissions == other.permissions
86
+ end
87
+
88
+ private
89
+
90
+ def _to_bcrypt_pw(pw)
91
+ Password.new(pw) if pw && !pw.empty?
92
+ end
93
+
94
+ def _run_validations
95
+ raise Errors::ChannelNameMissingError unless name
96
+ raise Errors::ChannelTokenMissingError unless token
97
+ raise Errors::ChannelSecretMissingError unless secret_hash_crt
98
+ end
99
+ end # Service
100
+ end # Channel
101
+ end # Ribbon::Intercom
@@ -0,0 +1,23 @@
1
+ module Ribbon::Intercom
2
+ class Service
3
+ module ChannelStores
4
+ class Base
5
+ def open_channel(params={})
6
+ raise NotImplementedError
7
+ end
8
+
9
+ def token_exists?(token)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ def lookup_channel(token)
14
+ raise NotImplementedError
15
+ end
16
+
17
+ def persist(channel)
18
+ raise NotImplementedError
19
+ end
20
+ end # Base
21
+ end # ChannelStores
22
+ end # Service
23
+ end # Ribbon::Intercom
@@ -0,0 +1,60 @@
1
+ require 'redis'
2
+
3
+ module Ribbon::Intercom
4
+ class Service
5
+ module ChannelStores
6
+ class RedisStore < Base
7
+ def initialize(params={})
8
+ if params[:url]
9
+ @_redis = Redis.new(url: params[:url])
10
+ elsif params[:redis]
11
+ @_redis = params[:redis]
12
+ else
13
+ raise Errors::InvalidStoreParamsError
14
+ end
15
+ end
16
+
17
+ def open_channel(params={})
18
+ Channel.new(self, params)
19
+ end
20
+
21
+ def token_exists?(token)
22
+ @_redis.exists("channel:#{token}")
23
+ end
24
+
25
+ def lookup_channel(token)
26
+ if token_exists?(token) && (channel_data = @_redis.hgetall("channel:#{token}"))
27
+ permissions = @_redis.smembers("channel:#{token}:permissions")
28
+
29
+ Channel.new(self,
30
+ Utils.symbolize_keys(
31
+ channel_data.merge(may: permissions)
32
+ )
33
+ )
34
+ end
35
+ end
36
+
37
+ def persist(channel)
38
+ raise Errors::InvalidChannelError, channel.inspect unless channel.is_a?(Channel)
39
+
40
+ data_hash = {}
41
+ [:name, :token, :secret_hash_crt, :secret_hash_prv].each { |key|
42
+ value = channel.send(key)
43
+ data_hash[key] = value if value
44
+ }
45
+
46
+ # Save channel data as hash
47
+ @_redis.mapped_hmset("channel:#{channel.token}", data_hash)
48
+
49
+ # Associate permissions set to its channel
50
+ channel_key = "channel:#{channel.token}:permissions"
51
+ channel.tap { |c|
52
+ c.permissions.each { |p|
53
+ @_redis.sadd(channel_key, p)
54
+ }
55
+ }
56
+ end
57
+ end # RedisStore
58
+ end # ChannelStores
59
+ end # Service
60
+ end # Ribbon::Intercom
@@ -0,0 +1,30 @@
1
+ module Ribbon::Intercom
2
+ class Utils
3
+ class << self
4
+ def sanitize(object)
5
+ case object
6
+ when String, Symbol, TrueClass, FalseClass, Integer, Float, NilClass
7
+ object
8
+ when Hash
9
+ sanitize_hash(object)
10
+ when Array
11
+ sanitize_array(object)
12
+ else
13
+ raise Errors::UnsafeValueError, object.inspect
14
+ end
15
+ end
16
+
17
+ def sanitize_array(array)
18
+ array.map { |obj| sanitize(obj) }
19
+ end
20
+
21
+ def sanitize_hash(hash)
22
+ Hash[hash.map { |key, val| [sanitize(key), sanitize(val)] }]
23
+ end
24
+
25
+ def symbolize_keys(hash)
26
+ hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
27
+ end
28
+ end # Class methods
29
+ end # Utils
30
+ end # Ribbon::Intercom
@@ -0,0 +1,5 @@
1
+ module Ribbon
2
+ module Intercom
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ require 'optparse'
2
+ require 'ribbon/intercom'
3
+ require 'json'
4
+
5
+ namespace :intercom do
6
+ namespace :client do
7
+ task :rotate_secret, [:remote] => :environment do |t, args|
8
+ remote = args[:remote]
9
+ puts "New #{remote} secret: #{Intercom[remote].rotate_secret}"
10
+ end
11
+ end
12
+
13
+ namespace :service do
14
+ task :open_channel, [:service_name, :channel_name] => :environment do |t, args|
15
+ service = args[:service_name].split("_").map(&:capitalize).join
16
+ channel_name = args[:channel_name]
17
+
18
+ Intercom::const_get(service).open_channel(name: channel_name)
19
+ puts "New channel: #{channel_name}"
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,212 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ribbon-intercom
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Honer
8
+ - Kayvon Ghaffari
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-04-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.5'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.5.2
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '1.5'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.5.2
34
+ - !ruby/object:Gem::Dependency
35
+ name: bcrypt
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.1.3
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 3.1.3
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - "~>"
49
+ - !ruby/object:Gem::Version
50
+ version: 3.1.3
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 3.1.3
54
+ - !ruby/object:Gem::Dependency
55
+ name: redis
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rest-client
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 1.7.2
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.7.2
82
+ - !ruby/object:Gem::Dependency
83
+ name: ribbon-config
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 0.1.0
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.1.0
96
+ - !ruby/object:Gem::Dependency
97
+ name: rspec
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rspec-rails
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: sqlite3
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: rails
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: 4.0.0
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: 4.0.0
152
+ - !ruby/object:Gem::Dependency
153
+ name: mock_redis
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ description: A standard for authenticating requests among services
167
+ email:
168
+ - robert@ribbonpayments.com
169
+ - kayvon@ribbon.co
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - lib/ribbon/intercom.rb
175
+ - lib/ribbon/intercom/client.rb
176
+ - lib/ribbon/intercom/client/sdk.rb
177
+ - lib/ribbon/intercom/client/sdk/connection.rb
178
+ - lib/ribbon/intercom/client/sdk/response.rb
179
+ - lib/ribbon/intercom/errors.rb
180
+ - lib/ribbon/intercom/railtie.rb
181
+ - lib/ribbon/intercom/service.rb
182
+ - lib/ribbon/intercom/service/channel.rb
183
+ - lib/ribbon/intercom/service/channel_stores/base.rb
184
+ - lib/ribbon/intercom/service/channel_stores/redis_store.rb
185
+ - lib/ribbon/intercom/utils.rb
186
+ - lib/ribbon/intercom/version.rb
187
+ - lib/tasks/intercom.rake
188
+ homepage: http://github.com/ribbon/intercom
189
+ licenses:
190
+ - BSD
191
+ metadata: {}
192
+ post_install_message:
193
+ rdoc_options: []
194
+ require_paths:
195
+ - lib
196
+ required_ruby_version: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ required_rubygems_version: !ruby/object:Gem::Requirement
202
+ requirements:
203
+ - - ">="
204
+ - !ruby/object:Gem::Version
205
+ version: '0'
206
+ requirements: []
207
+ rubyforge_project:
208
+ rubygems_version: 2.4.3
209
+ signing_key:
210
+ specification_version: 4
211
+ summary: A standard for authenticating requests among services
212
+ test_files: []