bone 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,441 @@
1
+
2
+ class Bone
3
+ module API
4
+ module InstanceMethods
5
+ attr_accessor :token, :secret
6
+ def initialize(t, s=nil)
7
+ # TODO: Add size command
8
+ @token, @secret = t, s
9
+ end
10
+ def get(name)
11
+ carefully do
12
+ raise_errors
13
+ Bone.api.get token, secret, name
14
+ end
15
+ end
16
+ alias_method :[], :get
17
+ def set(name, value)
18
+ carefully do
19
+ raise_errors
20
+ Bone.api.set token, secret, name, value
21
+ end
22
+ end
23
+ alias_method :[]=, :set
24
+ def keys(filter='*')
25
+ carefully do
26
+ raise_errors
27
+ Bone.api.keys(token, secret, filter) || []
28
+ end
29
+ end
30
+ def key?(name)
31
+ carefully do
32
+ raise_errors
33
+ Bone.api.key? token, secret, name
34
+ end
35
+ end
36
+ def register(this_token, this_secret)
37
+ carefully do
38
+ Bone.api.register this_token, this_secret
39
+ end
40
+ end
41
+ def generate
42
+ carefully do
43
+ Bone.api.generate || []
44
+ end
45
+ end
46
+ def destroy(token)
47
+ carefully do
48
+ Bone.api.destroy token, secret
49
+ end
50
+ end
51
+ def token?(token)
52
+ carefully do
53
+ Bone.api.token? token, secret
54
+ end
55
+ end
56
+ private
57
+ def raise_errors
58
+ raise RuntimeError, "No token" unless token
59
+ #raise RuntimeError, "Invalid token (#{token})" if !Bone.api.token?(token)
60
+ end
61
+ def carefully
62
+ begin
63
+ yield
64
+ rescue Errno::ECONNREFUSED => ex
65
+ Bone.info ex.message
66
+ nil
67
+ rescue => ex
68
+ Bone.ld "#{ex.class}: #{ex.message}", ex.backtrace
69
+ nil
70
+ end
71
+ end
72
+ end
73
+ module ClassMethods
74
+ def get(name)
75
+ new(Bone.token, Bone.secret).get name
76
+ end
77
+ alias_method :[], :get
78
+ def set(name, value)
79
+ new(Bone.token, Bone.secret).set name, value
80
+ end
81
+ alias_method :[]=, :set
82
+ def keys(filter='*')
83
+ new(Bone.token, Bone.secret).keys filter
84
+ end
85
+ def key?(name)
86
+ new(Bone.token, Bone.secret).key? name
87
+ end
88
+ def register(this_token, this_secret)
89
+ new(Bone.token, Bone.secret).register this_token, this_secret
90
+ end
91
+ def generate
92
+ new(Bone.token, Bone.secret).generate
93
+ end
94
+ def destroy(token)
95
+ new(Bone.token, Bone.secret).destroy token
96
+ end
97
+ def token?(token)
98
+ new(Bone.token, Bone.secret).token? token
99
+ end
100
+ end
101
+ module Helpers
102
+ def path(*parts)
103
+ "/#{APIVERSION}/" << parts.flatten.collect { |v| Bone.uri_escape(v) }.join('/')
104
+ end
105
+ def prefix(*parts)
106
+ parts.flatten!
107
+ parts.unshift *[APIVERSION, 'bone']
108
+ parts.join(':')
109
+ end
110
+ end
111
+ extend Bone::API::Helpers
112
+ end
113
+
114
+ module API
115
+
116
+ module HTTP
117
+ SIGVERSION = 'v2'.freeze unless defined?(Bone::API::HTTP::SIGVERSION)
118
+ @token_suffix = 2.freeze
119
+ class << self
120
+ attr_reader :token_suffix
121
+ # /v2/[name]
122
+ def get(token, secret, name)
123
+ path = Bone::API.path(token, 'key', name)
124
+ query = {}
125
+ http_request token, secret, :get, path, query
126
+ end
127
+ def set(token, secret, name, value)
128
+ path = Bone::API.path(token, 'key', name)
129
+ query = {}
130
+ http_request token, secret, :post, path, query, value
131
+ end
132
+ def keys(token, secret, filter='*')
133
+ path = Bone::API.path(token, 'keys')
134
+ ret = http_request token, secret, :get, path, {}
135
+ (ret || '').split($/)
136
+ end
137
+ def key?(token, secret, name)
138
+ !get(token, secret, name).nil?
139
+ end
140
+ def destroy(token, secret)
141
+ query = {}
142
+ path = Bone::API.path('destroy', token)
143
+ ret = http_request token, secret, :delete, path, query
144
+ !ret.nil? # errors return nil
145
+ end
146
+ def secret(token, secret)
147
+ path = Bone::API.path(token, 'secret')
148
+ ret = http_request token, secret, :get, path, {}
149
+ end
150
+ def register(token, secret)
151
+ query = {}
152
+ path = Bone::API.path('register', token)
153
+ http_request token, secret, :post, path, query, secret
154
+ end
155
+ def generate
156
+ path = Bone::API.path('generate')
157
+ ret = http_request '', '', :post, path, {}
158
+ ret.nil? ? nil : ret.split($/)
159
+ end
160
+ def token?(token, secret)
161
+ path = Bone::API.path(token)
162
+ query = {}
163
+ ret = http_request token, secret, :get, path, query
164
+ !ret.nil?
165
+ end
166
+ def connect
167
+ require 'em-http-request' # TODO: catch error, deregister this API
168
+ @external_em = EM.reactor_running?
169
+ #@retry_delay, @redirects, @max_retries, @performed_retries = 2, 1, 2, 0
170
+ end
171
+
172
+ def canonical_time now=Time.now
173
+ now.utc.to_i
174
+ end
175
+
176
+ def canonical_host host
177
+ if URI === host
178
+ host.port ||= 80
179
+ host = [host.host.to_s, host.port.to_s].join(':')
180
+ end
181
+ host.downcase
182
+ end
183
+
184
+ # Based on / stolen from: https://github.com/chneukirchen/rack/blob/master/lib/rack/utils.rb
185
+ # which was based on / stolen from Mongrel
186
+ def parse_query(qs, d = '&;')
187
+ params = {}
188
+ (qs || '').split(/[#{d}] */n).each do |p|
189
+ k, v = p.split('=', 2).map { |x| Bone.uri_unescape(x) }
190
+ if cur = params[k]
191
+ if cur.class == Array
192
+ params[k] << v
193
+ else
194
+ params[k] = [cur, v]
195
+ end
196
+ else
197
+ params[k] = v
198
+ end
199
+ end
200
+ return params
201
+ end
202
+
203
+ # Builds the canonical string for signing requests. This strips out all '&', '?', and '='
204
+ # from the query string to be signed. The parameters in the path passed in must already
205
+ # be sorted in case-insensitive alphabetical order and must not be url encoded.
206
+ #
207
+ # Based on / stolen from: https://github.com/grempe/amazon-ec2/blob/master/lib/AWS.rb
208
+ #
209
+ # See also: http://docs.amazonwebservices.com/AWSEC2/2009-04-04/DeveloperGuide/index.html?using-query-api.html
210
+ #
211
+ def canonical_sig_string host, meth, path, query, body=nil
212
+ # Sort, and encode parameters into a canonical string.
213
+ sorted_params = query.sort {|x,y| x[0].to_s <=> y[0].to_s }
214
+ encoded_params = sorted_params.collect do |p|
215
+ encoded = [Bone.uri_escape(p[0]), Bone.uri_escape(p[1])].join '='
216
+ # Ensure spaces are encoded as '%20', not '+'
217
+ encoded = encoded.gsub '+', '%20'
218
+ # According to RFC3986 (the scheme for values expected
219
+ # by signing requests), '~' should not be encoded
220
+ encoded = encoded.gsub '%7E', '~'
221
+ end
222
+ querystr = encoded_params.join '&'
223
+ parts = [meth.to_s.downcase, canonical_host(host), path, querystr]
224
+ parts << body unless body.to_s.empty?
225
+ parts.join "\n"
226
+ end
227
+
228
+ # Encodes the given string with the secret_access_key by taking the
229
+ # hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
230
+ # url encode the result of that to protect the string if it's going to
231
+ # be used as a query string parameter.
232
+ #
233
+ # Based on / stolen from: https://github.com/grempe/amazon-ec2/blob/master/lib/AWS.rb
234
+ def encode secret, str, escape=true
235
+ digest = OpenSSL::HMAC.digest Bone.digest_type.new, secret.to_s, str.to_s
236
+ b64_hmac = Base64.encode64(digest).tr "\n", ''
237
+ escape ? Bone.uri_escape(b64_hmac) : b64_hmac
238
+ end
239
+
240
+ def prepare_query query={}, token=Bone.token, stamp=canonical_time
241
+ { "sigversion" => Bone::API::HTTP::SIGVERSION,
242
+ "apiversion" => Bone::APIVERSION,
243
+ "token" => token,
244
+ "stamp" => stamp
245
+ }.merge query
246
+ end
247
+
248
+ def sign_query token, secret, meth, path, query, body=nil
249
+ sig = generate_signature secret, Bone.source, meth, path, query, body
250
+ { 'sig' => sig }.merge query
251
+ end
252
+
253
+ # Based on / stolen from: https://github.com/grempe/amazon-ec2/blob/master/lib/AWS.rb
254
+ def generate_signature secret, host, meth, path, query, body=nil
255
+ str = canonical_sig_string host, meth, path, query, body
256
+ sig = encode secret, str
257
+ Bone.ld [sig, str, body].inspect
258
+ sig
259
+ end
260
+
261
+ private
262
+
263
+ # based on: https://github.com/EmmanuelOga/firering/blob/master/lib/firering/connection.rb
264
+ def http_request token, secret, meth, path, query={}, body=nil
265
+ uri = Bone.source.clone
266
+ uri.path = path
267
+ query = prepare_query query, token
268
+ signed_query = sign_query token, secret, meth, path, query, body
269
+ Bone.ld "#{meth} #{uri} (#{query})"
270
+ content, status, headers = nil
271
+ handler = Proc.new do |http|
272
+ content, status, headers = http.response, http.response_header.status, http.response_header
273
+ end
274
+ if @external_em
275
+ em_request meth, uri, signed_query, body, &handler
276
+ else
277
+ EM.run {
278
+ em_request meth, uri, signed_query, body, &handler
279
+ }
280
+ end
281
+ if status >= 400
282
+ Bone.ld "Request failed: #{status} #{content}"
283
+ nil
284
+ else
285
+ content
286
+ end
287
+ end
288
+
289
+ def em_request meth, uri, query, body, &blk
290
+ args = { :query => query, :timeout => 10 }
291
+ args[:head] = {}
292
+ args[:body] = body.to_s unless body.nil?
293
+ http = EventMachine::HttpRequest.new(uri).send(meth, args)
294
+ http.errback do
295
+ #perform_retry(http) do
296
+ # http(method, path, data, &callback)
297
+ #end
298
+ Bone.info "Could not access #{uri}"
299
+ EventMachine.stop @external_em
300
+ end
301
+ http.callback {
302
+ Bone.ld "#{http.response_header.status}: #{http.response_header.inspect}"
303
+ #reset_retries_counter
304
+ blk.call(http) if blk
305
+ EventMachine.stop unless @external_em
306
+ }
307
+ http
308
+ end
309
+
310
+ end
311
+ Bone.register_api :http, self
312
+ Bone.register_api :https, self
313
+ end
314
+ module Redis
315
+ @token_suffix = 1.freeze
316
+ extend self
317
+ attr_reader :token_suffix
318
+ attr_accessor :redis
319
+ def get(token, secret, name)
320
+ Key.new(token, name).value.get # get returns nil if not set
321
+ end
322
+ def set(token, secret, name, value)
323
+ Key.new(token, name).value = value
324
+ Token.new(token).keys.add Time.now.utc.to_f, name
325
+ value.to_s
326
+ end
327
+ def keys(token, secret, filter='*')
328
+ Token.new(token).keys.to_a
329
+ end
330
+ def key?(token, secret, name)
331
+ Key.new(token, name).value.exists?
332
+ end
333
+ def destroy(token, secret)
334
+ Token.tokens.delete token
335
+ Token.new(token).secret.destroy!
336
+ end
337
+ def register(token, secret)
338
+ raise RuntimeError, "Could not generate token" if token.nil? || token?(token)
339
+ Token.tokens.add Time.now.utc.to_i, token
340
+ t = Token.new(token).secret = secret
341
+ token
342
+ end
343
+ def generate
344
+ begin
345
+ token = Bone.random_token
346
+ attempts ||= 10
347
+ end while token?(token) && !(attempts -= 1).zero?
348
+ secret = Bone.random_secret
349
+ raise RuntimeError, "Could not generate token" if token.nil? || token?(token)
350
+ Token.tokens.add Time.now.utc.to_i, token
351
+ t = Token.new(token).secret = secret
352
+ [token, secret]
353
+ end
354
+ def secret token
355
+ Token.new(token).secret.value
356
+ end
357
+ def token?(token, secret=nil)
358
+ Token.tokens.member?(token.to_s)
359
+ end
360
+ def connect
361
+ Familia.uri = Bone.source
362
+ end
363
+ class Key
364
+ include Familia
365
+ prefix Bone::API.prefix(:key)
366
+ string :value
367
+ attr_reader :token, :name, :bucket
368
+ def initialize(token, name, bucket=:global)
369
+ @token, @name, @bucket = token.to_s, name.to_s, bucket.to_s
370
+ initialize_redis_objects
371
+ end
372
+ def index
373
+ [token, bucket, name].join ':'
374
+ end
375
+ end
376
+ class Token
377
+ include Familia
378
+ prefix Bone::API.prefix(:token)
379
+ string :secret
380
+ zset :keys
381
+ class_zset :tokens
382
+ index :token
383
+ attr_reader :token
384
+ def initialize(token)
385
+ @token = token.to_s
386
+ initialize_redis_objects
387
+ end
388
+ end
389
+
390
+ Bone.register_api :redis, self
391
+ end
392
+
393
+ module Memory
394
+ extend self
395
+ @token_suffix = 0.freeze
396
+ attr_reader :token_suffix
397
+ @data, @tokens = {}, {}
398
+ def get(token, secret, name)
399
+ @data[Bone::API.prefix(token, name)]
400
+ end
401
+ def set(token, secret, name, value)
402
+ @data[Bone::API.prefix(token, name)] = value.to_s
403
+ end
404
+ def keys(token, secret, filter='*')
405
+ filter = '.+' if filter == '*'
406
+ filter = Bone::API.prefix(token, filter)
407
+ @data.keys.select { |name| name =~ /#{filter}/ }
408
+ end
409
+ def key?(token, secret, name)
410
+ @data.has_key?(Bone::API.prefix(token, name))
411
+ end
412
+ def destroy(token, secret)
413
+ @tokens.delete token
414
+ end
415
+ def register(token, secret)
416
+ raise RuntimeError, "Could not generate token" if token.nil? || token?(token)
417
+ @tokens[token] = secret
418
+ token
419
+ end
420
+ def secret(token)
421
+ @tokens[token]
422
+ end
423
+ def generate
424
+ begin
425
+ token = Bone.random_token
426
+ attemps ||= 10
427
+ end while token?(token) && !(attempts -= 1).zero?
428
+ secret = Bone.random_secret
429
+ @tokens[token] = secret
430
+ [token, secret]
431
+ end
432
+ def token?(token, secret=nil)
433
+ @tokens.key?(token)
434
+ end
435
+ def connect
436
+ end
437
+ Bone.register_api :memory, self
438
+ end
439
+
440
+ end
441
+ end
@@ -1,39 +1,49 @@
1
1
  require 'bone'
2
- require 'net/http'
2
+
3
+ # TODO: finish this
3
4
 
4
5
  class Bone::CLI < Drydock::Command
5
6
 
6
7
  def check!
7
- @token = @global.t || ENV['BONE_TOKEN']
8
- raise Bone::BadBone, @token unless Bone.valid_token?(@token)
8
+ @token = @global.token || ENV['BONE_TOKEN']
9
+ raise Bone::NoToken, @token unless Bone.token?(@token)
10
+ Bone.token = @token
9
11
  end
10
12
 
11
13
  def get
12
14
  check!
13
15
  @argv.unshift @alias unless @alias == 'get'
14
- raise "No key specified" unless @argv.first
15
- puts Bone.get(@argv.first)
16
- end
17
-
18
- def del
19
- check!
20
- raise "No key specified" unless @argv.first
21
- puts Bone.del(@argv.first)
16
+ ## TODO: handle bone name=value
17
+ ##if @alias.index('=') > 0
18
+ ## a = @alias.gsub(/\s+=\s+/, '=')
19
+ ## name, value = *( ? @argv.first.split('=') : @argv)
20
+ ##end
21
+ raise Bone::Problem, "No key specified" unless @argv.first
22
+ ret = Bone.get(@argv.first)
23
+ puts ret unless ret.nil?
22
24
  end
23
25
 
24
26
  def set
27
+ # TODO: use STDIN instead of @option.string
25
28
  check!
26
- opts = {:token => @token }
27
- keyname, value = *(@argv.size == 1 ? @argv.first.split('=') : @argv)
28
- raise "No key specified" unless keyname
29
- raise "No value specified" unless value
30
- if File.exists?(value) && !@option.string
31
- value = File.readlines(value).join
32
- opts[:file] = true
29
+ name, value = *(@argv.size == 1 ? @argv.first.split('=') : @argv)
30
+ raise Bone::Problem, "No key specified" unless name
31
+ from_stdin = false
32
+ if value.nil? && !stdin.tty? && !stdin.eof?
33
+ from_stdin = true
34
+ value = stdin.read
33
35
  end
34
- puts Bone.set(keyname, value, opts)
36
+ raise Bone::Problem, "Cannot set null value" unless value
37
+ Bone[name] = value
38
+ puts from_stdin ? '<STDIN>' : value
35
39
  end
36
40
 
41
+ #def del
42
+ # check!
43
+ # raise Bone::Problem, "No key specified" unless @argv.first
44
+ # puts Bone.delete(@argv.first)
45
+ #end
46
+
37
47
  def keys
38
48
  check!
39
49
  list = Bone.keys(@argv[0])
@@ -47,23 +57,27 @@ class Bone::CLI < Drydock::Command
47
57
  end
48
58
 
49
59
  def token
50
- check!
51
- if @option.force
52
- generate_token_dialog
53
- else
54
- puts @token
60
+ check!
61
+ puts Bone.token
62
+ end
63
+
64
+ def secret
65
+ check!
66
+ puts Bone.secret
67
+ end
68
+
69
+ def generate
70
+ t, s = *Bone.generate
71
+ unless t.nil?
72
+ puts "# Your token for #{Bone.source}"
73
+ puts "BONE_TOKEN=#{t}"
74
+ puts "BONE_SECRET=#{s}"
55
75
  end
56
- rescue Bone::BadBone => ex
57
- generate_token_dialog
58
- exit 1
76
+ #rescue Bone::NoToken => ex
77
+ # update_token_dialog
78
+ # exit 1
59
79
  end
60
80
 
61
81
  private
62
- def generate_token_dialog
63
- newtoken = Bone.generate_token
64
- puts newtoken and return if @global.quiet
65
- puts "Set the BONE_TOKEN environment variable with the following token"
66
- puts newtoken
67
- end
68
82
 
69
83
  end