angus-remote 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
1
  require 'angus/remote/client'
2
2
  require 'angus/remote/builder'
3
3
  require 'angus/remote/service_directory'
4
- require 'angus/remote/response/serializer'
4
+ require 'angus/remote/response/serializer'
5
+ require 'angus/remote/settings'
@@ -0,0 +1,72 @@
1
+ require 'digest'
2
+
3
+ require_relative 'redis_client'
4
+
5
+ module Angus
6
+ module Authentication
7
+ class Client
8
+
9
+ DEFAULT_PUBLIC_KEY = '1234567'
10
+ DEFAULT_PRIVATE_KEY = 'CHANGE ME!!'
11
+
12
+ AUTHENTICATION_HEADER = 'AUTHORIZATION'
13
+ BAAS_AUTHENTICATION_HEADER = 'X-BAAS-AUTH'
14
+ BAAS_SESSION_HEADER = 'X-Baas-Session-Seed'
15
+ DATE_HEADER = 'DATE'
16
+
17
+ def initialize(settings)
18
+ @public_key = settings[:public_key] || DEFAULT_PUBLIC_KEY
19
+ @private_key = settings[:private_key] || DEFAULT_PRIVATE_KEY
20
+ @store = RedisStore.new(settings[:store] || {})
21
+ end
22
+
23
+ def prepare_request(request, http_method, script_name)
24
+ date = Date.today
25
+
26
+ auth_token = generate_auth_token(date, http_method, script_name)
27
+ request[DATE_HEADER] = date.httpdate
28
+ request[AUTHENTICATION_HEADER] = generate_auth_header(auth_token)
29
+
30
+ session_auth_token = generate_session_auth_token(date, http_method, script_name)
31
+ request[BAAS_AUTHENTICATION_HEADER] = generate_auth_header(session_auth_token)
32
+ end
33
+
34
+ def store_session_private_key(response)
35
+ session_key_seed = extract_session_key_seed(response)
36
+ return unless session_key_seed
37
+
38
+ session_key = generate_session_private(session_key_seed)
39
+
40
+ @store.store_session_key(@public_key, session_key)
41
+ end
42
+
43
+ private
44
+
45
+ def generate_session_auth_token(date, http_method, script_name)
46
+ session_private_key = @store.get_session_key(@public_key)
47
+ Digest::SHA1.hexdigest("#{session_private_key}\n#{auth_data(date, http_method, script_name)}")
48
+ end
49
+
50
+ def generate_auth_token(date, http_method, script_name)
51
+ Digest::SHA1.hexdigest("#@private_key\n#{auth_data(date, http_method, script_name)}")
52
+ end
53
+
54
+ def extract_session_key_seed(response)
55
+ response[BAAS_SESSION_HEADER]
56
+ end
57
+
58
+ def auth_data(date, http_method, script_name)
59
+ "#{date.httpdate}\n#{http_method}\n#{script_name}"
60
+ end
61
+
62
+ def generate_session_private(key_seed)
63
+ Digest::SHA1.hexdigest("#@private_key\n#{key_seed}")
64
+ end
65
+
66
+ def generate_auth_header(auth_token)
67
+ "#@public_key:#{auth_token}"
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,34 @@
1
+ require 'redis'
2
+
3
+ module Angus
4
+ module Authentication
5
+
6
+ class RedisStore
7
+
8
+ DEFAULT_NAMESPACE = ''
9
+
10
+ def initialize(settings)
11
+ @namespace = settings.delete(:namespace) || DEFAULT_NAMESPACE
12
+ @settings = settings
13
+ end
14
+
15
+ def store_session_key(key, data)
16
+ redis.set(add_namespace(key), data)
17
+ end
18
+
19
+ def get_session_key(key)
20
+ redis.get(add_namespace(key))
21
+ end
22
+
23
+ def redis
24
+ @redis ||= Redis.new(@settings)
25
+ end
26
+
27
+ def add_namespace(key)
28
+ "#@namespace.angus-authentication-client.#{key}"
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+ end
@@ -15,10 +15,11 @@ module Angus
15
15
  # @param [String] code_name The service's name known to the service directory
16
16
  # @param [Angus::SDoc::Definitions::Service] service_definition
17
17
  # @param api_url Base service api url
18
+ # @param [Hash] options
18
19
  #
19
20
  # @return [Remote::Client] object that implements each method specified as operation
20
21
  # in the service metadata
21
- def self.build(code_name, service_definition, api_url)
22
+ def self.build(code_name, service_definition, api_url, options)
22
23
  remote_service_class = build_client_class(service_definition.name)
23
24
 
24
25
  # TODO: define how to use the namespace in the remote client.
@@ -43,7 +44,7 @@ module Angus
43
44
  end
44
45
  end
45
46
 
46
- remote_service_class.new(api_url, self.default_timeout)
47
+ remote_service_class.new(api_url, self.default_timeout, options)
47
48
  end
48
49
 
49
50
  def self.define_operation(client_class, namespace, operation, service_code_name,
@@ -58,11 +59,13 @@ module Angus
58
59
  path_params = Angus::Remote::Builder.extract_var_arg!(args, Array) || []
59
60
  encode_as_json = Angus::Remote::Builder.extract_var_arg!(args, TrueClass) || false
60
61
 
61
- request_params = Angus::Remote::Builder.apply_glossary(service_definition.glossary, request_params)
62
+ request_params = Angus::Remote::Builder.apply_glossary(service_definition.glossary,
63
+ request_params)
64
+
62
65
  request_params = Angus::Remote::Builder.escape_request_params(request_params)
63
66
 
64
- response = make_request(operation.path, operation.method, encode_as_json, path_params,
65
- request_params)
67
+ response = make_request(operation.path, operation.http_method, encode_as_json,
68
+ path_params, request_params)
66
69
 
67
70
  Angus::Remote::Response::Builder.build_from_remote_response(response,
68
71
  service_code_name,
@@ -95,8 +98,8 @@ module Angus
95
98
 
96
99
  request_params = Angus::Remote::Builder.escape_request_params(request_params)
97
100
 
98
- response = make_request(operation.path, operation.method, encode_as_json, path_params,
99
- request_params)
101
+ response = make_request(operation.path, operation.http_method, encode_as_json,
102
+ path_params, request_params)
100
103
 
101
104
  Angus::Remote::Response::Builder.build_from_remote_response(response,
102
105
  service_code_name,
@@ -186,10 +189,10 @@ module Angus
186
189
  end
187
190
  encoded[encoded_name] = value
188
191
  end
192
+
189
193
  encoded
190
194
  end
191
195
 
192
-
193
196
  def self.default_timeout
194
197
  @default_timeout || DEFAULT_TIMEOUT
195
198
  end
@@ -201,4 +204,4 @@ module Angus
201
204
  end
202
205
 
203
206
  end
204
- end
207
+ end
@@ -5,13 +5,14 @@ require_relative 'exceptions'
5
5
  require_relative 'utils'
6
6
 
7
7
  require_relative 'response/builder'
8
+ require_relative 'settings'
8
9
 
9
10
  module Angus
10
11
  module Remote
11
12
 
12
13
  # A client for service invocation
13
14
  class Client
14
- def initialize(api_url, timeout = nil)
15
+ def initialize(api_url, timeout = nil, options = {})
15
16
  api_url = api_url[0..-2] if api_url[-1] == '/'
16
17
 
17
18
  @connection = PersistentHTTP.new(
@@ -26,6 +27,12 @@ module Angus
26
27
  )
27
28
 
28
29
  @api_base_path = @connection.default_path
30
+
31
+ client_settings = { :public_key => options['public_key'],
32
+ :private_key => options['private_key'],
33
+ :store => Settings.redis }
34
+
35
+ @authentication_client = Authentication::Client.new(client_settings)
29
36
  end
30
37
 
31
38
  # Makes a request to the service
@@ -51,8 +58,12 @@ module Angus
51
58
  request = Utils.build_request(method, path, request_params, encode_as_json)
52
59
 
53
60
  begin
61
+ @authentication_client.prepare_request(request, method.upcase, path)
62
+
54
63
  response = @connection.request(request)
55
64
 
65
+ @authentication_client.store_session_private_key(response)
66
+
56
67
  if Utils.severe_error_response?(response)
57
68
  raise RemoteSevereError.new(get_error_messages(response.body))
58
69
  end
@@ -73,6 +84,7 @@ module Angus
73
84
  json_response = JSON(response_body) rescue { 'messages' => [] }
74
85
  Response::Builder::build_messages(json_response['messages'])
75
86
  end
87
+
76
88
  end
77
89
 
78
90
  end
@@ -11,6 +11,16 @@ module Angus
11
11
 
12
12
  end
13
13
 
14
+ class RemoteSevereError < Exception
15
+
16
+ attr_reader :messages
17
+
18
+ def initialize(messages)
19
+ @messages = messages
20
+ end
21
+
22
+ end
23
+
14
24
  class RemoteConnectionError < Exception
15
25
 
16
26
  def initialize(url)
@@ -1,7 +1,9 @@
1
+ require 'digest'
1
2
  require 'json'
2
3
  require 'yaml'
3
4
 
4
5
  require 'angus/sdoc'
6
+ require 'angus/authentication/client'
5
7
 
6
8
  require_relative 'builder'
7
9
  require_relative 'exceptions'
@@ -17,7 +19,6 @@ module Angus
17
19
 
18
20
  # Builds and returns a Client object for the service and version received
19
21
  def self.lookup(code_name, version = nil)
20
-
21
22
  version ||= service_version(code_name)
22
23
 
23
24
  @clients_cache ||= {}
@@ -28,7 +29,8 @@ module Angus
28
29
  begin
29
30
  service_definition = self.service_definition(code_name, version)
30
31
  client = Angus::Remote::Builder.build(code_name, service_definition,
31
- self.api_url(code_name, version))
32
+ self.api_url(code_name, version),
33
+ service_settings(code_name, version))
32
34
  @clients_cache[[code_name, version]] = client
33
35
 
34
36
  rescue Errno::ECONNREFUSED
@@ -78,7 +80,7 @@ module Angus
78
80
 
79
81
  config = service_configuration(code_name)
80
82
 
81
- config["v#{version}"]["api_url"]
83
+ config["v#{version}"]['api_url']
82
84
  end
83
85
 
84
86
  # Returns the configured version.
@@ -98,20 +100,6 @@ module Angus
98
100
  end
99
101
  end
100
102
 
101
- # Returns the connection configuration for a given service.
102
- #
103
- # @param [String] code_name Service name
104
- #
105
- # @return [Hash]
106
- #
107
- # @raise [ServiceConfigurationNotFound] When no configuration for the given service
108
- def self.service_configuration(code_name)
109
- @services_configuration ||= YAML.load_file(SERVICES_CONFIGURATION_FILE)
110
-
111
- @services_configuration[code_name] or
112
- raise ServiceConfigurationNotFound.new(code_name)
113
- end
114
-
115
103
  # Returns the service's definition for the given service name and version
116
104
  #
117
105
  # @param [String] code_name Service that acts as a proxy.
@@ -153,7 +141,7 @@ module Angus
153
141
 
154
142
  proxy_doc_url = self.proxy_doc_url(code_name, version, remote_code_name)
155
143
 
156
- definition_hash = fetch_remote_service_definition(proxy_doc_url)
144
+ definition_hash = fetch_remote_service_definition(proxy_doc_url, code_name, version)
157
145
 
158
146
  proxy_service_definition = Angus::SDoc::DefinitionsReader.build_service_definition(definition_hash)
159
147
 
@@ -177,7 +165,7 @@ module Angus
177
165
  if doc_url.match('file://(.*)') || doc_url.match('file:///(.*)')
178
166
  Angus::SDoc::DefinitionsReader.service_definition($1)
179
167
  else
180
- definition_hash = fetch_remote_service_definition(doc_url)
168
+ definition_hash = fetch_remote_service_definition(doc_url, code_name, version)
181
169
  Angus::SDoc::DefinitionsReader.build_service_definition(definition_hash)
182
170
  end
183
171
  end
@@ -185,10 +173,11 @@ module Angus
185
173
  # Fetches a service definition from a remote http uri.
186
174
  #
187
175
  # @param [String] uri URI that publishes a service definition.
188
- # That uri should handle JSON format.
176
+ # @param [String] code_name Name of the service.
177
+ # @param [String] version Version of the service.
189
178
  #
190
179
  # @return [Hash] Service definition hash
191
- def self.fetch_remote_service_definition(uri)
180
+ def self.fetch_remote_service_definition(uri, code_name, version)
192
181
  uri = URI(uri)
193
182
  uri.query = URI.encode_www_form({:format => :json})
194
183
 
@@ -202,15 +191,68 @@ module Angus
202
191
  response = connection.start do |http|
203
192
  request = Net::HTTP::Get.new(uri.request_uri)
204
193
 
194
+ authentication_client(code_name, version).prepare_request(request, 'GET', uri.path)
195
+
205
196
  http.request(request)
206
197
  end
207
198
 
199
+ authentication_client(code_name, version).store_session_private_key(response)
200
+
208
201
  JSON(response.body)
209
202
  rescue Exception
210
203
  raise RemoteConnectionError.new(uri)
211
204
  end
212
205
  private_class_method :fetch_remote_service_definition
213
206
 
207
+ def self.authentication_client(code_name, version)
208
+ @authentication_clients ||= {}
209
+
210
+ unless @authentication_clients.include?([code_name, version])
211
+ service_settings = service_settings(code_name, version)
212
+
213
+ settings = { :public_key => service_settings['public_key'],
214
+ :private_key => service_settings['private_key'],
215
+ :store => Settings.redis.merge({ :namespace => "#{code_name}.#{version}" }) }
216
+
217
+ @authentication_clients[[code_name, version]] = Angus::Authentication::Client.new(settings)
218
+ end
219
+
220
+ @authentication_clients[[code_name, version]]
221
+ end
222
+ private_class_method :authentication_client
223
+
224
+ # Returns the documentation url from the configuration file
225
+ #
226
+ # @param [String] code_name Name of the service.
227
+ # @param [String] version Version of the service.
228
+ #
229
+ # If no version given, it reads the version from the configuration file.
230
+ #
231
+ # @raise (see .service_version)
232
+ def self.service_settings(code_name, version = nil)
233
+ version ||= service_version(code_name)
234
+
235
+ config = service_configuration(code_name)
236
+
237
+ config["v#{version}"]
238
+ end
239
+ private_class_method :service_settings
240
+
241
+ # Returns the connection configuration for a given service.
242
+ #
243
+ # @param [String] code_name Service name
244
+ #
245
+ # @return [Hash]
246
+ #
247
+ # @raise [ServiceConfigurationNotFound] When no configuration for the given service
248
+ def self.service_configuration(code_name)
249
+ @services_configuration ||= YAML.load_file(SERVICES_CONFIGURATION_FILE)
250
+
251
+ @services_configuration[code_name] or
252
+ raise ServiceConfigurationNotFound.new(code_name)
253
+ end
254
+ private_class_method :service_configuration
255
+
214
256
  end
215
257
 
216
258
  end
@@ -0,0 +1,29 @@
1
+ require 'uri'
2
+
3
+ require_relative 'client'
4
+ require_relative 'response/builder'
5
+
6
+ module Angus
7
+ module Remote
8
+
9
+ module Settings
10
+
11
+ def self.add_option(name, default_value)
12
+ define_singleton_method(name) do
13
+ instance_variable_get("@#{name}") || default_value
14
+ end
15
+
16
+ define_singleton_method("#{name}=") do |value|
17
+ instance_variable_set("@#{name}", value)
18
+ end
19
+ end
20
+
21
+ DEFAULT_TIMEOUT = 60
22
+
23
+ add_option(:default_timeout, DEFAULT_TIMEOUT)
24
+ add_option(:redis, {})
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -1,7 +1,7 @@
1
1
  module Angus
2
2
  module Remote
3
3
 
4
- VERSION = '0.0.2'
4
+ VERSION = '0.0.3'
5
5
 
6
6
  end
7
7
  end
@@ -10,8 +10,8 @@ describe Angus::Remote::Builder do
10
10
  describe '.build' do
11
11
 
12
12
  let(:code_name) { 'vpos' }
13
- let(:operation) { double(:operation, :code_name=> 'get_users', :service_name => 'vpos', :path => '/users', :method => :get) }
14
- let(:proxy_operation) { double(:proxy_operation, :code_name=> 'get_users_proxy', :service_name => 'vpos', :path => '/users', :method => :get) }
13
+ let(:operation) { double(:operation, :code_name=> 'get_users', :service_name => 'vpos', :path => '/users', :http_method => :get) }
14
+ let(:proxy_operation) { double(:proxy_operation, :code_name=> 'get_users_proxy', :service_name => 'vpos', :path => '/users', :http_method => :get) }
15
15
  let(:glossary) { double(:glossary, :terms_hash_with_long_names => {}) }
16
16
  let(:service_definition) { double(:vpos, :name => 'Vpos', :operations => { 'users' => [operation] },
17
17
  :proxy_operations => { 'users' => [proxy_operation] }, :version => '0.1', :glossary => glossary) }
@@ -20,7 +20,7 @@ describe Angus::Remote::Builder do
20
20
 
21
21
  describe 'the returned class' do
22
22
 
23
- subject(:client) { builder.build(code_name, service_definition, api_url) }
23
+ subject(:client) { builder.build(code_name, service_definition, api_url, {}) }
24
24
 
25
25
  it 'is of class Angus::Remote::Client' do
26
26
  should be_kind_of(Angus::Remote::Client)
@@ -17,11 +17,33 @@ describe Angus::Remote::Client do
17
17
 
18
18
  describe '#make_request' do
19
19
 
20
- it 'returns the remote service response' do
21
- response = double(:response, :code => 200, :body => '[]')
22
- PersistentHTTP.any_instance.stub(:request => response)
20
+ let(:success_response) { double(:response, :code => 200, :body => '[]') }
21
+ let(:authentication_client) { double(:authentication_client) }
22
+
23
+ before do
24
+ Angus::Authentication::Client.stub(:new => authentication_client)
25
+ authentication_client.stub(:prepare_request => nil, :store_session_private_key => nil)
26
+ PersistentHTTP.any_instance.stub(:request => success_response)
27
+ end
28
+
29
+ it 'prepares the request with authentication' do
30
+ client.make_request('/users', 'get', false, [], {})
31
+
32
+ authentication_client.should have_received(:prepare_request).with(kind_of(Net::HTTP::Get),
33
+ 'GET', '//users')
34
+ end
23
35
 
24
- client.make_request('/users', 'get', false, [], {}).should eq(response)
36
+ it 'extracts the authentication data from the response' do
37
+ client.make_request('/users', 'get', false, [], {})
38
+
39
+ authentication_client.should have_received(
40
+ :store_session_private_key
41
+ ).with(success_response)
42
+ end
43
+
44
+
45
+ it 'returns the remote service response' do
46
+ client.make_request('/users', 'get', false, [], {}).should eq(success_response)
25
47
  end
26
48
 
27
49
  context 'when an invalid method is used' do
@@ -52,7 +52,7 @@ describe Angus::Remote::ServiceDirectory do
52
52
 
53
53
  it 'gets the definition hash from the remote service' do
54
54
  service_directory.should_receive(:fetch_remote_service_definition).with(
55
- doc_url
55
+ doc_url, code_name, version
56
56
  ).and_return(definition_hash)
57
57
 
58
58
  service_directory.get_service_definition(code_name, version)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: angus-remote
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-10-31 00:00:00.000000000 Z
13
+ date: 2013-11-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: angus-sdoc
@@ -60,6 +60,22 @@ dependencies:
60
60
  - - ~>
61
61
  - !ruby/object:Gem::Version
62
62
  version: '1.2'
63
+ - !ruby/object:Gem::Dependency
64
+ name: redis
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
63
79
  - !ruby/object:Gem::Dependency
64
80
  name: rake
65
81
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +124,22 @@ dependencies:
108
124
  - - ~>
109
125
  - !ruby/object:Gem::Version
110
126
  version: '2.14'
127
+ - !ruby/object:Gem::Dependency
128
+ name: mock_redis
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
111
143
  - !ruby/object:Gem::Dependency
112
144
  name: simplecov
113
145
  requirement: !ruby/object:Gem::Requirement
@@ -189,6 +221,7 @@ files:
189
221
  - lib/angus/remote/http/multipart_methods/multipart_post.rb
190
222
  - lib/angus/remote/http/multipart.rb
191
223
  - lib/angus/remote/message.rb
224
+ - lib/angus/remote/settings.rb
192
225
  - lib/angus/remote/builder.rb
193
226
  - lib/angus/remote/proxy_client_utils.rb
194
227
  - lib/angus/remote/response/builder.rb
@@ -200,6 +233,8 @@ files:
200
233
  - lib/angus/remote/client.rb
201
234
  - lib/angus/remote/proxy_client.rb
202
235
  - lib/angus/remote/utils.rb
236
+ - lib/angus/authentication/client.rb
237
+ - lib/angus/authentication/redis_store.rb
203
238
  - spec/angus/remote/service_directory_spec.rb
204
239
  - spec/angus/remote/proxy_client_utils_spec.rb
205
240
  - spec/angus/remote/http/query_params_spec.rb