angus-remote 0.0.2 → 0.0.3

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.
@@ -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