haveapi-client 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|