haveapi-client 0.1.1 → 0.2.0
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 +4 -4
- data/LICENSE.txt +21 -0
- data/README.md +18 -8
- data/haveapi-client.gemspec +1 -1
- data/lib/haveapi/cli.rb +2 -2
- data/lib/haveapi/cli/authentication/base.rb +52 -0
- data/lib/haveapi/cli/authentication/basic.rb +28 -0
- data/lib/haveapi/cli/authentication/token.rb +65 -0
- data/lib/haveapi/cli/cli.rb +109 -26
- data/lib/haveapi/client/action.rb +15 -0
- data/lib/haveapi/client/authentication/base.rb +79 -0
- data/lib/haveapi/client/authentication/basic.rb +9 -0
- data/lib/haveapi/client/authentication/noauth.rb +5 -0
- data/lib/haveapi/client/authentication/token.rb +59 -0
- data/lib/haveapi/client/client.rb +7 -5
- data/lib/haveapi/client/communicator.rb +40 -11
- data/lib/haveapi/client/version.rb +1 -1
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f73740e620eeb403f3cdec3491598f9b48bcfc4
|
4
|
+
data.tar.gz: 3e4d8544316b4a47f948f573de365055021725fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 037eee5721e264c732be091ee8fea13e0c96c63e824c8422b4a31e21b503f7e5be3e27114984e71dca37c7237f6c3a8f9392a7206bdd9e381757c0c42fac8d86
|
7
|
+
data.tar.gz: c891d03e167e84a0d7103498a65bfcee3146094de96124d998ea5a4d74d182001dc6609c6e0a58d6d39e327a52e04580cc36662287b8633d8e38345e200f88a0
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Jakub Skokan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
@@ -21,13 +21,15 @@ Or install it yourself as:
|
|
21
21
|
### CLI
|
22
22
|
$ haveapi-cli -h
|
23
23
|
Usage: haveapi-cli [options] <resource> <action> [objects ids] [-- [parameters]]
|
24
|
-
-
|
24
|
+
-u, --api URL API URL
|
25
|
+
-a, --auth METHOD Authentication method
|
25
26
|
--list-versions List all available API versions
|
26
|
-
--list-
|
27
|
-
|
27
|
+
--list-auth-methods [VERSION]
|
28
|
+
List available authentication methods
|
29
|
+
--list-resources [VERSION] List all resource in API version
|
30
|
+
--list-actions [VERSION] List all resources and actions in API version
|
28
31
|
-r, --raw Print raw response as is
|
29
|
-
-
|
30
|
-
-p, --password PASSWORD Password
|
32
|
+
-s, --save Save credentials to config file for later use
|
31
33
|
-v, --[no-]verbose Run verbosely
|
32
34
|
-h, --help Show this message
|
33
35
|
|
@@ -35,20 +37,28 @@ Using the API example from
|
|
35
37
|
[HaveAPI README](https://github.com/vpsfreecz/haveapi/blob/master/README.md#example),
|
36
38
|
users would be listed with:
|
37
39
|
|
38
|
-
$ haveapi-cli
|
40
|
+
$ haveapi-cli --url https://your.api.tld --auth basic --username yourname --password yourpassword user index
|
39
41
|
|
40
42
|
Nested resources and object IDs:
|
41
43
|
|
42
|
-
$ haveapi-cli
|
44
|
+
$ haveapi-cli --url https://your.api.tld --auth basic --username yourname --password yourpassword user.invoice index 10
|
43
45
|
|
44
46
|
where `10` is user ID.
|
47
|
+
|
48
|
+
User credentials can be saved to a config:
|
49
|
+
|
50
|
+
$ haveapi-cli --url https://your.api.tld --auth basic --username yourname --password yourpassword --save user index
|
51
|
+
|
52
|
+
When saved, they don't have to be specified as command line options:
|
53
|
+
|
54
|
+
$ haveapi-cli --url https://your.api.tld user index
|
45
55
|
|
46
56
|
### Client library
|
47
57
|
```ruby
|
48
58
|
require 'haveapi/client'
|
49
59
|
|
50
60
|
api = HaveAPI::Client::Client.new('https://your.api.tld')
|
51
|
-
api.
|
61
|
+
api.authenticate(:basic, user: 'yourname', password: 'yourpassword')
|
52
62
|
|
53
63
|
response = api.user.index
|
54
64
|
p response.ok?
|
data/haveapi-client.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.summary =
|
12
12
|
spec.description = 'Ruby API and CLI for HaveAPI'
|
13
13
|
spec.homepage = ''
|
14
|
-
spec.license = '
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
data/lib/haveapi/cli.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative 'client'
|
2
|
+
require_rel 'cli'
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module HaveAPI::CLI
|
2
|
+
module Authentication
|
3
|
+
# Base class for CLI interface of authentication providers
|
4
|
+
class Base
|
5
|
+
class << self
|
6
|
+
# Register this class as authentication provider with +name+.
|
7
|
+
# The +name+ must be the same as is used in client auth provider
|
8
|
+
# and on server side.
|
9
|
+
# All providers have to register.
|
10
|
+
def register(name)
|
11
|
+
HaveAPI::CLI::Cli.register_auth_method(name, Kernel.const_get(to_s))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :communicator
|
16
|
+
|
17
|
+
def initialize(opts = {})
|
18
|
+
opts ||= {}
|
19
|
+
|
20
|
+
opts.each do |k, v|
|
21
|
+
instance_variable_set("@#{k}", v)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Implement this method to add CLI options for auth provider.
|
26
|
+
# +opts+ is an instance of OptionParser.
|
27
|
+
# This method is NOT called if the auth provider has been loaded
|
28
|
+
# from the config and wasn't specified as a command line option
|
29
|
+
# and therefore all necessary information must be stored in the config.
|
30
|
+
def options(opts)
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
# Implement this method to check if all needed information
|
35
|
+
# for successful authentication are provided.
|
36
|
+
# Ask the user on stdin if something is missing.
|
37
|
+
def validate
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
# This method should call HaveAPI::Client::Communicator#authenticate
|
42
|
+
# with arguments specific for this authentication provider.
|
43
|
+
def authenticate
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def save
|
48
|
+
@communicator.auth_save
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module HaveAPI::CLI::Authentication
|
2
|
+
class Basic < Base
|
3
|
+
register :basic
|
4
|
+
|
5
|
+
def options(opts)
|
6
|
+
opts.on('--username USER', 'User name') do |u|
|
7
|
+
@user = u
|
8
|
+
end
|
9
|
+
|
10
|
+
opts.on('--password PASSWORD', 'Password') do |p|
|
11
|
+
@password = p
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate
|
16
|
+
@user ||= ask('User name: ') { |q| q.default = nil }.to_s
|
17
|
+
|
18
|
+
@password ||= ask('Password: ') do |q|
|
19
|
+
q.default = nil
|
20
|
+
q.echo = false
|
21
|
+
end.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def authenticate
|
25
|
+
@communicator.authenticate(:basic, {user: @user, password: @password})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module HaveAPI::CLI::Authentication
|
2
|
+
class Token < Base
|
3
|
+
register :token
|
4
|
+
|
5
|
+
def options(opts)
|
6
|
+
opts.on('--username USER', 'User name') do |u|
|
7
|
+
@user = u
|
8
|
+
end
|
9
|
+
|
10
|
+
opts.on('--password PASSWORD', 'Password') do |p|
|
11
|
+
@password = p
|
12
|
+
end
|
13
|
+
|
14
|
+
opts.on('--token TOKEN', 'Token') do |t|
|
15
|
+
@token = t
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on('--token-validity SECONDS', Integer,
|
19
|
+
'How long will token be valid in seconds, 0 for forever') do |s|
|
20
|
+
@validity = s
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('--new-token', 'Request new token') do
|
24
|
+
@token = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
via = %i(query_param header)
|
28
|
+
|
29
|
+
opts.on('--token-via VIA', via,
|
30
|
+
'Send token as a query parameter or in HTTP header',
|
31
|
+
"(#{via.join(', ')})") do |v|
|
32
|
+
@via = v.to_sym
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate
|
37
|
+
return if @token
|
38
|
+
|
39
|
+
@user ||= ask('User name: ') { |q| q.default = nil }
|
40
|
+
|
41
|
+
@password ||= ask('Password: ') do |q|
|
42
|
+
q.default = nil
|
43
|
+
q.echo = false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def authenticate
|
48
|
+
@communicator.authenticate(:token, {
|
49
|
+
user: @user,
|
50
|
+
password: @password,
|
51
|
+
token: @token,
|
52
|
+
validity: @validity,
|
53
|
+
valid_to: @valid_to,
|
54
|
+
via: @via
|
55
|
+
})
|
56
|
+
end
|
57
|
+
|
58
|
+
def save
|
59
|
+
super.update({
|
60
|
+
via: @via,
|
61
|
+
validity: @validity
|
62
|
+
})
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/haveapi/cli/cli.rb
CHANGED
@@ -2,17 +2,30 @@ require 'optparse'
|
|
2
2
|
require 'pp'
|
3
3
|
require 'highline/import'
|
4
4
|
require 'table_print'
|
5
|
+
require 'yaml'
|
5
6
|
|
6
7
|
module HaveAPI
|
7
8
|
module CLI
|
8
9
|
class Cli
|
9
|
-
|
10
|
-
|
10
|
+
class << self
|
11
|
+
attr_accessor :auth_methods
|
12
|
+
|
13
|
+
def run
|
14
|
+
c = new
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_auth_method(name, klass)
|
18
|
+
@auth_methods ||= {}
|
19
|
+
@auth_methods[name] = klass
|
20
|
+
end
|
11
21
|
end
|
12
22
|
|
13
23
|
def initialize
|
24
|
+
@config = read_config || {}
|
14
25
|
args, @opts = options
|
26
|
+
|
15
27
|
@api = HaveAPI::Client::Communicator.new(api_url)
|
28
|
+
@api.identity = $0.split('/').last
|
16
29
|
|
17
30
|
if @action
|
18
31
|
method(@action.first).call( * @action[1..-1] )
|
@@ -37,6 +50,8 @@ module HaveAPI
|
|
37
50
|
|
38
51
|
action = @api.get_action(resources, action, args[2..-1])
|
39
52
|
|
53
|
+
action.update_description(@api.describe_action(action)) if authenticate(action)
|
54
|
+
|
40
55
|
@input_params = parameters(action)
|
41
56
|
|
42
57
|
if action
|
@@ -78,14 +93,24 @@ module HaveAPI
|
|
78
93
|
@global_opt = OptionParser.new do |opts|
|
79
94
|
opts.banner = "Usage: #{$0} [options] <resource> <action> [objects ids] [-- [parameters]]"
|
80
95
|
|
81
|
-
opts.on('-
|
96
|
+
opts.on('-u', '--api URL', 'API URL') do |url|
|
82
97
|
options[:client] = url
|
83
98
|
end
|
84
99
|
|
100
|
+
opts.on('-a', '--auth METHOD', Cli.auth_methods.keys, 'Authentication method') do |m|
|
101
|
+
options[:auth] = m
|
102
|
+
@auth = Cli.auth_methods[m].new(server_config(options[:client])[:auth][m])
|
103
|
+
@auth.options(opts)
|
104
|
+
end
|
105
|
+
|
85
106
|
opts.on('--list-versions', 'List all available API versions') do
|
86
107
|
@action = [:list_versions]
|
87
108
|
end
|
88
109
|
|
110
|
+
opts.on('--list-auth-methods [VERSION]', 'List available authentication methods') do |v|
|
111
|
+
@action = [:list_auth, v && v.sub(/^v/, '')]
|
112
|
+
end
|
113
|
+
|
89
114
|
opts.on('--list-resources [VERSION]', 'List all resource in API version') do |v|
|
90
115
|
@action = [:list_resources, v && v.sub(/^v/, '')]
|
91
116
|
end
|
@@ -98,12 +123,8 @@ module HaveAPI
|
|
98
123
|
options[:raw] = true
|
99
124
|
end
|
100
125
|
|
101
|
-
opts.on('-
|
102
|
-
options[:
|
103
|
-
end
|
104
|
-
|
105
|
-
opts.on('-p', '--password PASSWORD', 'Password') do |p|
|
106
|
-
options[:password] = p
|
126
|
+
opts.on('-s', '--save', 'Save credentials to config file for later use') do
|
127
|
+
options[:save] = true
|
107
128
|
end
|
108
129
|
|
109
130
|
opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
|
@@ -127,8 +148,11 @@ module HaveAPI
|
|
127
148
|
|
128
149
|
@global_opt.parse!(args)
|
129
150
|
|
130
|
-
|
131
|
-
|
151
|
+
unless options[:auth]
|
152
|
+
cfg = server_config(options[:client])
|
153
|
+
|
154
|
+
@auth = Cli.auth_methods[cfg[:last_auth]].new(cfg[:auth][cfg[:last_auth]]) if cfg[:last_auth]
|
155
|
+
end
|
132
156
|
|
133
157
|
[args, options]
|
134
158
|
end
|
@@ -206,10 +230,18 @@ module HaveAPI
|
|
206
230
|
end
|
207
231
|
end
|
208
232
|
|
233
|
+
def list_auth(v=nil)
|
234
|
+
desc = @api.describe_api
|
235
|
+
|
236
|
+
desc[:versions][(v && v.to_sym) || desc[:default_version].to_s.to_sym][:authentication].each_key do |auth|
|
237
|
+
puts auth if Cli.auth_methods.has_key?(auth)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
209
241
|
def list_resources(v=nil)
|
210
242
|
desc = @api.describe_api
|
211
243
|
|
212
|
-
desc[:versions][v || desc[:default_version].to_s.to_sym][:resources].each do |resource, children|
|
244
|
+
desc[:versions][(v && v.to_sym) || desc[:default_version].to_s.to_sym][:resources].each do |resource, children|
|
213
245
|
nested_resource(resource, children, false)
|
214
246
|
end
|
215
247
|
end
|
@@ -217,7 +249,7 @@ module HaveAPI
|
|
217
249
|
def list_actions(v=nil)
|
218
250
|
desc = @api.describe_api
|
219
251
|
|
220
|
-
desc[:versions][v || desc[:default_version].to_s.to_sym][:resources].each do |resource, children|
|
252
|
+
desc[:versions][(v && v.to_sym) || desc[:default_version].to_s.to_sym][:resources].each do |resource, children|
|
221
253
|
nested_resource(resource, children, true)
|
222
254
|
end
|
223
255
|
end
|
@@ -271,17 +303,31 @@ module HaveAPI
|
|
271
303
|
|
272
304
|
return if response.empty?
|
273
305
|
|
274
|
-
s = action.structure
|
275
306
|
namespace = action.namespace(:output).to_sym
|
276
307
|
|
277
308
|
case action.layout.to_sym
|
278
309
|
when :list
|
279
|
-
|
310
|
+
cols = []
|
311
|
+
|
312
|
+
action.params.each do |name, p|
|
313
|
+
if p[:type] == 'Resource'
|
314
|
+
cols << {name => {display_method: ->(r) { r[name] && r[name][p[:value_label].to_sym] } }}
|
315
|
+
else
|
316
|
+
cols << name
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
tp response[namespace], *cols
|
280
321
|
|
281
322
|
|
282
323
|
when :object
|
283
324
|
response[namespace].each do |k, v|
|
284
|
-
|
325
|
+
|
326
|
+
if action.params[k][:type] == 'Resource'
|
327
|
+
puts "#{k}: #{v[action.params[k][:value_label].to_sym]}"
|
328
|
+
else
|
329
|
+
puts "#{k}: #{v}"
|
330
|
+
end
|
285
331
|
end
|
286
332
|
|
287
333
|
|
@@ -301,29 +347,66 @@ module HaveAPI
|
|
301
347
|
end
|
302
348
|
end
|
303
349
|
|
304
|
-
def
|
350
|
+
def authenticate(action)
|
305
351
|
if action.auth?
|
306
|
-
@
|
352
|
+
if @auth
|
353
|
+
@auth.communicator = @api
|
354
|
+
@auth.validate
|
355
|
+
@auth.authenticate
|
356
|
+
|
357
|
+
if @opts[:save]
|
358
|
+
cfg = server_config(api_url)
|
359
|
+
cfg[:auth][@opts[:auth]] = @auth.save
|
360
|
+
cfg[:last_auth] = @opts[:auth]
|
361
|
+
write_config
|
362
|
+
end
|
307
363
|
|
308
|
-
|
309
|
-
|
310
|
-
q.echo = false
|
364
|
+
else
|
365
|
+
# FIXME: exit as auth is needed and has not been selected
|
311
366
|
end
|
312
|
-
end
|
313
367
|
|
314
|
-
|
315
|
-
return false
|
368
|
+
return true
|
316
369
|
end
|
317
370
|
|
318
|
-
|
371
|
+
false
|
372
|
+
end
|
319
373
|
|
320
|
-
|
374
|
+
def params_valid?(action)
|
375
|
+
true # FIXME
|
321
376
|
end
|
322
377
|
|
323
378
|
protected
|
324
379
|
def default_url
|
325
380
|
'http://localhost:4567'
|
326
381
|
end
|
382
|
+
|
383
|
+
def config_path
|
384
|
+
"#{Dir.home}/.haveapi-client.yml"
|
385
|
+
end
|
386
|
+
|
387
|
+
def write_config
|
388
|
+
File.open(config_path, 'w') do |f|
|
389
|
+
f.write(YAML.dump(@config))
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def read_config
|
394
|
+
@config = YAML.load_file(config_path) if File.exists?(config_path)
|
395
|
+
end
|
396
|
+
|
397
|
+
def server_config(url)
|
398
|
+
unless @config[:servers]
|
399
|
+
@config[:servers] = [{url: url, auth: {}}]
|
400
|
+
return @config[:servers].first
|
401
|
+
end
|
402
|
+
|
403
|
+
@config[:servers].each do |s|
|
404
|
+
return s if s[:url] == url
|
405
|
+
end
|
406
|
+
|
407
|
+
@config[:servers] << {url: url, auth: {}}
|
408
|
+
@config[:servers].last
|
409
|
+
end
|
327
410
|
end
|
328
411
|
end
|
329
412
|
end
|
@@ -12,6 +12,7 @@ module HaveAPI
|
|
12
12
|
def execute(*args)
|
13
13
|
ret = @api.call(self, *args)
|
14
14
|
@prepared_url = nil
|
15
|
+
@prepared_help = nil
|
15
16
|
ret
|
16
17
|
end
|
17
18
|
|
@@ -51,11 +52,19 @@ module HaveAPI
|
|
51
52
|
@spec[:url]
|
52
53
|
end
|
53
54
|
|
55
|
+
def help
|
56
|
+
@spec[:help]
|
57
|
+
end
|
58
|
+
|
54
59
|
# Url with resolved parameters.
|
55
60
|
def prepared_url
|
56
61
|
@prepared_url || @spec[:url]
|
57
62
|
end
|
58
63
|
|
64
|
+
def prepared_help
|
65
|
+
@prepared_help || @spec[:help]
|
66
|
+
end
|
67
|
+
|
59
68
|
def http_method
|
60
69
|
@spec[:method]
|
61
70
|
end
|
@@ -68,12 +77,18 @@ module HaveAPI
|
|
68
77
|
apply_args(args)
|
69
78
|
end
|
70
79
|
|
80
|
+
def update_description(spec)
|
81
|
+
@spec = spec
|
82
|
+
end
|
83
|
+
|
71
84
|
private
|
72
85
|
def apply_args(args)
|
73
86
|
@prepared_url ||= @spec[:url].dup
|
87
|
+
@prepared_help ||= @spec[:help].dup
|
74
88
|
|
75
89
|
args.each do |arg|
|
76
90
|
@prepared_url.sub!(/:[a-zA-Z\-_]+/, arg.to_s)
|
91
|
+
@prepared_help.sub!(/:[a-zA-Z\-_]+/, arg.to_s)
|
77
92
|
end
|
78
93
|
end
|
79
94
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module HaveAPI::Client
|
2
|
+
module Authentication
|
3
|
+
# Raise this exception when authentication process fails somewhere
|
4
|
+
# outside action execution (in which access forbidden is raised from RestClient).
|
5
|
+
class AuthenticationFailed < Exception
|
6
|
+
def initialize(msg)
|
7
|
+
@msg = msg
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
@msg
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Base class for all authentication providers.
|
16
|
+
#
|
17
|
+
# Authentication providers may reimplement only methods they need.
|
18
|
+
# You do not have to reimplement all methods.
|
19
|
+
class Base
|
20
|
+
class << self
|
21
|
+
# Register this class as authentication provider with +name+.
|
22
|
+
# The +name+ must be the same as is used in CLI auth provider (if any)
|
23
|
+
# and on server side.
|
24
|
+
# All providers have to register.
|
25
|
+
def register(name)
|
26
|
+
HaveAPI::Client::Communicator.register_auth_method(name, Kernel.const_get(to_s))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(communicator, description, opts)
|
31
|
+
@communicator = communicator
|
32
|
+
@desc = description
|
33
|
+
@opts = opts
|
34
|
+
|
35
|
+
setup
|
36
|
+
end
|
37
|
+
|
38
|
+
# Called right after initialize. Use this method to initialize provider.
|
39
|
+
def setup
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return RestClient::Resource instance. This is mainly for HTTP basic auth.
|
44
|
+
def resource
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
# Called for each request. Returns a hash of query parameters.
|
49
|
+
def request_url_params
|
50
|
+
{}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Called for each request. Returns a hash of parameters send in request
|
54
|
+
# body.
|
55
|
+
def request_payload
|
56
|
+
{}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Called for each request. Returns a hash of HTTP headers.
|
60
|
+
def request_headers
|
61
|
+
{}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns a hash of auth provider attributes to be saved e.g. in a file
|
65
|
+
# to be used later, without the user providing credentials again.
|
66
|
+
# You may wish to save a username or password (not recommended), tokens
|
67
|
+
# or whatever authentication provider needs to authenticate user
|
68
|
+
# without his input.
|
69
|
+
def save
|
70
|
+
@opts
|
71
|
+
end
|
72
|
+
|
73
|
+
# Load auth provider attributes from previous #save call.
|
74
|
+
def load(hash)
|
75
|
+
@opts = hash
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module HaveAPI::Client::Authentication
|
2
|
+
class Token < Base
|
3
|
+
register :token
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@via = @opts[:via] || :header
|
7
|
+
@token = @opts[:token]
|
8
|
+
@valid_to = @opts[:valid_to]
|
9
|
+
|
10
|
+
request_token unless @token
|
11
|
+
|
12
|
+
@configured = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_url_params
|
16
|
+
return {} unless @configured
|
17
|
+
check_validity
|
18
|
+
@via == :query_param ? {@desc[:query_parameter] => @token} : {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def request_headers
|
22
|
+
return {} unless @configured
|
23
|
+
check_validity
|
24
|
+
@via == :header ? {@desc[:http_header] => @token} : {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def save
|
28
|
+
{token: @token, valid_to: @valid_to}
|
29
|
+
end
|
30
|
+
|
31
|
+
def load(hash)
|
32
|
+
@token = hash[:token]
|
33
|
+
@valid_to = hash[:valid_to]
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
def request_token
|
38
|
+
a = HaveAPI::Client::Action.new(@communicator, :request, @desc[:resources][:token][:actions][:request], [])
|
39
|
+
ret = a.execute({login: @opts[:user], password: @opts[:password], validity: @opts[:validity] || 300})
|
40
|
+
|
41
|
+
raise AuthenticationFailed.new('bad username or password') unless ret[:status]
|
42
|
+
|
43
|
+
@token = ret[:response][:token][:token]
|
44
|
+
|
45
|
+
@valid_to = ret[:response][:token][:valid_to]
|
46
|
+
@valid_to = @valid_to && DateTime.iso8601(@valid_to).to_time
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_validity
|
50
|
+
if @valid_to && @valid_to < Time.now
|
51
|
+
if @opts[:user] && @opts[:password]
|
52
|
+
request_token
|
53
|
+
else
|
54
|
+
raise AuthenticationFailed.new('token expired')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -3,15 +3,17 @@ require 'pp'
|
|
3
3
|
class HaveAPI::Client::Client
|
4
4
|
attr_reader :resources
|
5
5
|
|
6
|
-
def initialize(url, v=nil)
|
6
|
+
def initialize(url, v=nil, identity: 'haveapi-client')
|
7
7
|
@version = v
|
8
|
-
@api =
|
8
|
+
@api = HaveAPI::Client::Communicator.new(url, v)
|
9
|
+
@api.identity = identity
|
9
10
|
|
10
11
|
setup_api(@api.describe_api)
|
11
12
|
end
|
12
13
|
|
13
|
-
|
14
|
-
|
14
|
+
# See Communicator#authenticate.
|
15
|
+
def authenticate(*args)
|
16
|
+
@api.authenticate(*args)
|
15
17
|
end
|
16
18
|
|
17
19
|
private
|
@@ -21,7 +23,7 @@ class HaveAPI::Client::Client
|
|
21
23
|
@resources = {}
|
22
24
|
|
23
25
|
description[:versions][v.to_s.to_sym][:resources].each do |name, desc|
|
24
|
-
r =
|
26
|
+
r = HaveAPI::Client::Resource.new(@api, name)
|
25
27
|
r.setup(desc)
|
26
28
|
|
27
29
|
define_singleton_method(name) do |*args|
|
@@ -7,14 +7,39 @@ require_rel '../../restclient_ext'
|
|
7
7
|
module HaveAPI
|
8
8
|
module Client
|
9
9
|
class Communicator
|
10
|
-
|
10
|
+
class << self
|
11
|
+
attr_reader :auth_methods
|
12
|
+
|
13
|
+
def register_auth_method(name, klass)
|
14
|
+
@auth_methods ||= {}
|
15
|
+
@auth_methods[name] = klass
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :url
|
20
|
+
attr_accessor :identity
|
21
|
+
|
22
|
+
def initialize(url, v = nil)
|
11
23
|
@url = url
|
24
|
+
@auth = Authentication::NoAuth.new(self, {}, {})
|
12
25
|
@rest = RestClient::Resource.new(@url)
|
13
|
-
@version =
|
26
|
+
@version = v
|
27
|
+
@identity = 'haveapi-client-ruby'
|
14
28
|
end
|
15
29
|
|
16
|
-
|
17
|
-
|
30
|
+
# Authenticate user with selected +auth_method+.
|
31
|
+
# +auth_method+ is a name of registered authentication provider.
|
32
|
+
# +options+ are specific for each authentication provider.
|
33
|
+
def authenticate(auth_method, options = {})
|
34
|
+
desc = describe_api(@version)
|
35
|
+
desc = desc[:versions][desc[:default_version].to_s.to_sym] unless @version
|
36
|
+
|
37
|
+
@auth = self.class.auth_methods[auth_method].new(self, desc[:authentication][auth_method], options)
|
38
|
+
@rest = @auth.resource || @rest
|
39
|
+
end
|
40
|
+
|
41
|
+
def auth_save
|
42
|
+
@auth.save
|
18
43
|
end
|
19
44
|
|
20
45
|
def describe_api(v=nil)
|
@@ -23,7 +48,7 @@ module HaveAPI
|
|
23
48
|
|
24
49
|
def describe_resource(path)
|
25
50
|
api = describe_api
|
26
|
-
tmp =
|
51
|
+
tmp = api[:versions][ api[:default_version].to_s.to_sym ]
|
27
52
|
|
28
53
|
path.each do |r|
|
29
54
|
tmp = tmp[:resources][r.to_sym]
|
@@ -34,12 +59,13 @@ module HaveAPI
|
|
34
59
|
tmp
|
35
60
|
end
|
36
61
|
|
37
|
-
def describe_action(
|
38
|
-
|
62
|
+
def describe_action(action)
|
63
|
+
description_for(action.prepared_help)
|
39
64
|
end
|
40
65
|
|
41
66
|
def get_action(resources, action, args)
|
42
67
|
@spec ||= describe_api(@version)
|
68
|
+
@spec = @spec[:versions][@spec[:default_version].to_s.to_sym] unless @version
|
43
69
|
|
44
70
|
tmp = @spec
|
45
71
|
|
@@ -63,8 +89,8 @@ module HaveAPI
|
|
63
89
|
input_namespace = action.namespace(:input)
|
64
90
|
|
65
91
|
if %w(POST PUT).include?(action.http_method)
|
66
|
-
args << {input_namespace => params}.to_json
|
67
|
-
args << {:
|
92
|
+
args << {input_namespace => params}.update(@auth.request_payload).to_json
|
93
|
+
args << {content_type: :json, accept: :json, user_agent: @identity}.update(@auth.request_headers)
|
68
94
|
|
69
95
|
elsif %w(GET DELETE).include?(action.http_method)
|
70
96
|
get_params = {}
|
@@ -73,7 +99,7 @@ module HaveAPI
|
|
73
99
|
get_params["#{input_namespace}[#{k}]"] = v
|
74
100
|
end
|
75
101
|
|
76
|
-
args << {params: get_params, accept: :json}
|
102
|
+
args << {params: get_params.update(@auth.request_url_params), accept: :json, user_agent: @identity}.update(@auth.request_headers)
|
77
103
|
end
|
78
104
|
|
79
105
|
begin
|
@@ -117,7 +143,10 @@ module HaveAPI
|
|
117
143
|
end
|
118
144
|
|
119
145
|
def description_for(path)
|
120
|
-
parse(@rest[path].get_options
|
146
|
+
parse(@rest[path].get_options({
|
147
|
+
params: @auth.request_payload.update(@auth.request_url_params),
|
148
|
+
user_agent: @identity
|
149
|
+
}.update(@auth.request_headers)))
|
121
150
|
end
|
122
151
|
|
123
152
|
def parse(str)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: haveapi-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakub Skokan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -131,14 +131,22 @@ extensions: []
|
|
131
131
|
extra_rdoc_files: []
|
132
132
|
files:
|
133
133
|
- Gemfile
|
134
|
+
- LICENSE.txt
|
134
135
|
- README.md
|
135
136
|
- Rakefile
|
136
137
|
- bin/haveapi-cli
|
137
138
|
- haveapi-client.gemspec
|
138
139
|
- lib/haveapi/cli.rb
|
140
|
+
- lib/haveapi/cli/authentication/base.rb
|
141
|
+
- lib/haveapi/cli/authentication/basic.rb
|
142
|
+
- lib/haveapi/cli/authentication/token.rb
|
139
143
|
- lib/haveapi/cli/cli.rb
|
140
144
|
- lib/haveapi/client.rb
|
141
145
|
- lib/haveapi/client/action.rb
|
146
|
+
- lib/haveapi/client/authentication/base.rb
|
147
|
+
- lib/haveapi/client/authentication/basic.rb
|
148
|
+
- lib/haveapi/client/authentication/noauth.rb
|
149
|
+
- lib/haveapi/client/authentication/token.rb
|
142
150
|
- lib/haveapi/client/client.rb
|
143
151
|
- lib/haveapi/client/communicator.rb
|
144
152
|
- lib/haveapi/client/exceptions.rb
|
@@ -150,7 +158,7 @@ files:
|
|
150
158
|
- lib/restclient_ext/resource.rb
|
151
159
|
homepage: ''
|
152
160
|
licenses:
|
153
|
-
-
|
161
|
+
- MIT
|
154
162
|
metadata: {}
|
155
163
|
post_install_message:
|
156
164
|
rdoc_options: []
|