haveapi 0.12.1 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/doc/protocol.md +27 -10
- data/haveapi.gemspec +1 -2
- data/lib/haveapi/action.rb +23 -7
- data/lib/haveapi/actions/default.rb +3 -3
- data/lib/haveapi/authentication/base.rb +19 -1
- data/lib/haveapi/authentication/basic/provider.rb +7 -1
- data/lib/haveapi/authentication/chain.rb +10 -12
- data/lib/haveapi/authentication/token/action_config.rb +53 -0
- data/lib/haveapi/authentication/token/action_request.rb +23 -0
- data/lib/haveapi/authentication/token/action_result.rb +42 -0
- data/lib/haveapi/authentication/token/config.rb +115 -0
- data/lib/haveapi/authentication/token/provider.rb +259 -81
- data/lib/haveapi/authentication/token.rb +9 -0
- data/lib/haveapi/client_examples/curl.rb +3 -3
- data/lib/haveapi/client_examples/fs_client.rb +3 -3
- data/lib/haveapi/client_examples/http.rb +7 -7
- data/lib/haveapi/client_examples/js_client.rb +1 -1
- data/lib/haveapi/client_examples/php_client.rb +1 -1
- data/lib/haveapi/client_examples/ruby_cli.rb +1 -1
- data/lib/haveapi/client_examples/ruby_client.rb +1 -1
- data/lib/haveapi/context.rb +13 -13
- data/lib/haveapi/example.rb +3 -3
- data/lib/haveapi/exceptions.rb +2 -0
- data/lib/haveapi/extensions/exception_mailer.rb +8 -1
- data/lib/haveapi/model_adapters/active_record.rb +6 -6
- data/lib/haveapi/parameters/resource.rb +10 -10
- data/lib/haveapi/params.rb +1 -1
- data/lib/haveapi/resource.rb +18 -10
- data/lib/haveapi/resources/action_state.rb +2 -2
- data/lib/haveapi/route.rb +4 -3
- data/lib/haveapi/server.rb +7 -7
- data/lib/haveapi/spec/mock_action.rb +3 -3
- data/lib/haveapi/spec/spec_methods.rb +8 -8
- data/lib/haveapi/version.rb +2 -2
- data/lib/haveapi/views/version_page.erb +2 -2
- data/shell.nix +1 -1
- metadata +9 -5
- data/lib/haveapi/authentication/token/resources.rb +0 -110
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'haveapi/authentication/base'
|
2
|
+
require 'haveapi/resource'
|
3
|
+
require 'haveapi/action'
|
2
4
|
|
3
5
|
module HaveAPI::Authentication
|
4
6
|
module Token
|
@@ -8,15 +10,18 @@ module HaveAPI::Authentication
|
|
8
10
|
|
9
11
|
end
|
10
12
|
|
11
|
-
# Provider for token authentication.
|
12
|
-
# and implemented.
|
13
|
+
# Provider for token authentication.
|
13
14
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# authenticating the user. Client sends the token with each request
|
17
|
-
# in configured #http_header or #query_parameter.
|
15
|
+
# This provider has to be configured using
|
16
|
+
# {HaveAPI::Authentication::Token::Config}.
|
18
17
|
#
|
19
|
-
# Token can
|
18
|
+
# Token auth contains API resource `token`. User can request a token by
|
19
|
+
# calling action `Request`. The returned token is then used for
|
20
|
+
# authenticating the user. Client sends the token with each request in
|
21
|
+
# configured {HaveAPI::Authentication::Token::Config#http_header} or
|
22
|
+
# {HaveAPI::Authentication::Token::Config#query_parameter}.
|
23
|
+
#
|
24
|
+
# Token can be revoked by calling action `Revoke` and renewed with `Renew`.
|
20
25
|
#
|
21
26
|
# === \Example usage:
|
22
27
|
#
|
@@ -34,31 +39,60 @@ module HaveAPI::Authentication
|
|
34
39
|
# end
|
35
40
|
# end
|
36
41
|
#
|
37
|
-
# Authentication provider:
|
38
|
-
# class
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
42
|
+
# Authentication provider configuration:
|
43
|
+
# class MyTokenAuthConfig < HaveAPI::Authentication::Token::Config
|
44
|
+
# request do
|
45
|
+
# handle do |req, res|
|
46
|
+
# user = ::User.find_by(login: input[:user], password: input[:password])
|
47
|
+
#
|
48
|
+
# if user.nil?
|
49
|
+
# res.error = 'invalid user or password'
|
50
|
+
# next res
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# token = SecureRandom.hex(50)
|
54
|
+
# valid_to =
|
55
|
+
# if req.input[:lifetime] == 'permanent'
|
56
|
+
# nil
|
57
|
+
# else
|
58
|
+
# Time.now + req.input[:interval]
|
59
|
+
#
|
60
|
+
# user.tokens << ::Token.new(
|
61
|
+
# token: token,
|
62
|
+
# lifetime: req.input[:lifetime],
|
63
|
+
# valid_to: valid_to,
|
64
|
+
# interval: req.input[:interval],
|
65
|
+
# label: req.request.user_agent,
|
66
|
+
# )
|
45
67
|
#
|
46
|
-
#
|
47
|
-
#
|
68
|
+
# res.token = token
|
69
|
+
# res.valid_to = valid_to
|
70
|
+
# res.complete = true
|
71
|
+
# res.ok
|
72
|
+
# end
|
48
73
|
# end
|
49
74
|
#
|
50
|
-
#
|
51
|
-
#
|
75
|
+
# renew do
|
76
|
+
# handle do |req, res|
|
77
|
+
# t = ::Token.find_by(user: req.user, token: req.token)
|
52
78
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
79
|
+
# if t && t.lifetime.start_with('renewable')
|
80
|
+
# t.renew
|
81
|
+
# t.save
|
82
|
+
# res.valid_to = t.valid_to
|
83
|
+
# res.ok
|
84
|
+
# else
|
85
|
+
# res.error = 'unable to renew token'
|
86
|
+
# res
|
87
|
+
# end
|
57
88
|
# end
|
58
89
|
# end
|
59
90
|
#
|
60
|
-
#
|
61
|
-
#
|
91
|
+
# revoke do
|
92
|
+
# handle do |req, res|
|
93
|
+
# req.user.tokens.delete(token: req.token)
|
94
|
+
# res.ok
|
95
|
+
# end
|
62
96
|
# end
|
63
97
|
#
|
64
98
|
# def find_user_by_token(request, token)
|
@@ -79,94 +113,238 @@ module HaveAPI::Authentication
|
|
79
113
|
# Finally put the provider in the authentication chain:
|
80
114
|
# api = HaveAPI.new(...)
|
81
115
|
# ...
|
82
|
-
# api.auth_chain <<
|
116
|
+
# api.auth_chain << HaveAPI::Authentication::Token.with_config(MyTokenAuthConfig)
|
83
117
|
class Provider < Base
|
118
|
+
auth_method :token
|
119
|
+
|
120
|
+
# Configure the token provider
|
121
|
+
# @param cfg [Config]
|
122
|
+
def self.with_config(cfg)
|
123
|
+
Module.new do
|
124
|
+
define_singleton_method(:new) do |*args|
|
125
|
+
Provider.new(*args, cfg)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
attr_reader :config
|
131
|
+
|
132
|
+
def initialize(server, v, cfg)
|
133
|
+
@config = cfg.new(server, v)
|
134
|
+
super(server, v)
|
135
|
+
end
|
136
|
+
|
84
137
|
def setup
|
85
|
-
|
86
|
-
|
138
|
+
@server.allow_header(config.class.http_header)
|
139
|
+
end
|
87
140
|
|
88
|
-
|
141
|
+
def resource_module
|
142
|
+
return @module if @module
|
143
|
+
provider = self
|
144
|
+
|
145
|
+
@module = Module.new do
|
146
|
+
const_set(:Token, provider.send(:token_resource))
|
147
|
+
end
|
89
148
|
end
|
90
149
|
|
150
|
+
# Authenticate request
|
151
|
+
# @param request [Sinatra::Request]
|
91
152
|
def authenticate(request)
|
92
153
|
t = token(request)
|
93
154
|
|
94
|
-
t && find_user_by_token(request, t)
|
155
|
+
t && config.find_user_by_token(request, t)
|
95
156
|
end
|
96
157
|
|
158
|
+
# Extract token from HTTP request
|
159
|
+
# @param request [Sinatra::Request]
|
160
|
+
# @return [String]
|
97
161
|
def token(request)
|
98
|
-
request[query_parameter] || request.env[header_to_env]
|
162
|
+
request[config.class.query_parameter] || request.env[header_to_env]
|
99
163
|
end
|
100
164
|
|
101
165
|
def describe
|
102
166
|
{
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
167
|
+
http_header: config.class.http_header,
|
168
|
+
query_parameter: config.class.query_parameter,
|
169
|
+
description: "The client authenticates with credentials, usually "+
|
170
|
+
"username and password, and gets a token. "+
|
171
|
+
"From this point, the credentials can be forgotten and "+
|
172
|
+
"the token is used instead. Tokens can have different lifetimes, "+
|
173
|
+
"can be renewed and revoked. The token is passed either via HTTP "+
|
174
|
+
"header or query parameter."
|
110
175
|
}
|
111
176
|
end
|
112
177
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
'X-HaveAPI-Auth-Token'
|
178
|
+
private
|
179
|
+
def header_to_env
|
180
|
+
"HTTP_#{config.class.http_header.upcase.gsub(/\-/, '_')}"
|
117
181
|
end
|
118
182
|
|
119
|
-
|
120
|
-
|
121
|
-
:_auth_token
|
122
|
-
end
|
183
|
+
def token_resource
|
184
|
+
provider = self
|
123
185
|
|
124
|
-
|
125
|
-
|
126
|
-
SecureRandom.hex(50)
|
127
|
-
end
|
186
|
+
HaveAPI::Resource.define_resource(:Token) do
|
187
|
+
define_singleton_method(:token_instance) { provider }
|
128
188
|
|
129
|
-
|
130
|
-
|
131
|
-
# Returns a date time which is token expiration.
|
132
|
-
# It is up to the implementation of this method to remember
|
133
|
-
# token lifetime and interval.
|
134
|
-
# Must be implemented.
|
135
|
-
def save_token(request, user, token, lifetime, interval)
|
189
|
+
auth false
|
190
|
+
version :all
|
136
191
|
|
137
|
-
|
192
|
+
define_action(:Request) do
|
193
|
+
route ''
|
194
|
+
http_method :post
|
138
195
|
|
139
|
-
|
140
|
-
|
141
|
-
|
196
|
+
input(:hash) do
|
197
|
+
if block = provider.config.class.request.input
|
198
|
+
instance_exec(&block)
|
199
|
+
end
|
142
200
|
|
143
|
-
|
201
|
+
string :lifetime, label: 'Lifetime', required: true,
|
202
|
+
choices: %i(fixed renewable_manual renewable_auto permanent),
|
203
|
+
desc: <<END
|
204
|
+
fixed - the token has a fixed validity period, it cannot be renewed
|
205
|
+
renewable_manual - the token can be renewed, but it must be done manually via renew action
|
206
|
+
renewable_auto - the token is renewed automatically to now+interval every time it is used
|
207
|
+
permanent - the token will be valid forever, unless deleted
|
208
|
+
END
|
209
|
+
integer :interval, label: 'Interval',
|
210
|
+
desc: 'How long will requested token be valid, in seconds.',
|
211
|
+
default: 60*5, fill: true
|
212
|
+
end
|
144
213
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
214
|
+
output(:hash) do
|
215
|
+
string :token
|
216
|
+
datetime :valid_to
|
217
|
+
bool :complete
|
218
|
+
string :next_action
|
219
|
+
end
|
149
220
|
|
150
|
-
|
221
|
+
authorize do
|
222
|
+
allow
|
223
|
+
end
|
151
224
|
|
152
|
-
|
153
|
-
|
154
|
-
# Must be implemented.
|
155
|
-
def find_user_by_credentials(request, username, password)
|
225
|
+
def exec
|
226
|
+
config = self.class.resource.token_instance.config
|
156
227
|
|
157
|
-
|
228
|
+
begin
|
229
|
+
result = config.class.request.handle.call(ActionRequest.new(
|
230
|
+
request: request,
|
231
|
+
input: input,
|
232
|
+
), ActionResult.new)
|
233
|
+
rescue HaveAPI::AuthenticationError => e
|
234
|
+
error(e.message)
|
235
|
+
end
|
158
236
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
# Must be implemented.
|
163
|
-
def find_user_by_token(request, token)
|
237
|
+
unless result.ok?
|
238
|
+
error(result.error || 'invalid authentication credentials')
|
239
|
+
end
|
164
240
|
|
165
|
-
|
241
|
+
{
|
242
|
+
token: result.token,
|
243
|
+
valid_to: result.valid_to,
|
244
|
+
complete: result.complete?,
|
245
|
+
next_action: result.next_action,
|
246
|
+
}
|
247
|
+
end
|
248
|
+
end
|
166
249
|
|
167
|
-
|
168
|
-
|
169
|
-
|
250
|
+
define_action(:Revoke) do
|
251
|
+
http_method :post
|
252
|
+
auth true
|
253
|
+
|
254
|
+
authorize do
|
255
|
+
allow
|
256
|
+
end
|
257
|
+
|
258
|
+
def exec
|
259
|
+
provider = self.class.resource.token_instance
|
260
|
+
result = provider.config.class.revoke.handle.call(ActionRequest.new(
|
261
|
+
request: request,
|
262
|
+
user: current_user,
|
263
|
+
token: provider.token(request),
|
264
|
+
), ActionResult.new)
|
265
|
+
|
266
|
+
if result.ok?
|
267
|
+
ok
|
268
|
+
else
|
269
|
+
error(result.error || 'revoke failed')
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
define_action(:Renew) do
|
275
|
+
http_method :post
|
276
|
+
auth true
|
277
|
+
|
278
|
+
output(:hash) do
|
279
|
+
datetime :valid_to
|
280
|
+
end
|
281
|
+
|
282
|
+
authorize do
|
283
|
+
allow
|
284
|
+
end
|
285
|
+
|
286
|
+
def exec
|
287
|
+
provider = self.class.resource.token_instance
|
288
|
+
result = provider.config.renew_token(ActionRequest.new(
|
289
|
+
request: request,
|
290
|
+
user: current_user,
|
291
|
+
token: provider.token(request),
|
292
|
+
), ActionResult.new)
|
293
|
+
|
294
|
+
if result.ok?
|
295
|
+
{valid_to: result.valid_to}
|
296
|
+
else
|
297
|
+
error(result.error || 'renew failed')
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
provider.config.class.actions.each do |name, config|
|
303
|
+
define_action(:"#{name.to_s.capitalize}") do
|
304
|
+
http_method :post
|
305
|
+
auth false
|
306
|
+
|
307
|
+
input(:hash) do
|
308
|
+
string :token, required: true
|
309
|
+
instance_exec(&config.input) if config.input
|
310
|
+
end
|
311
|
+
|
312
|
+
output(:hash) do
|
313
|
+
string :token
|
314
|
+
datetime :valid_to
|
315
|
+
bool :complete
|
316
|
+
string :next_action
|
317
|
+
end
|
318
|
+
|
319
|
+
authorize do
|
320
|
+
allow
|
321
|
+
end
|
322
|
+
|
323
|
+
define_method(:exec) do
|
324
|
+
begin
|
325
|
+
result = config.handle.call(ActionRequest.new(
|
326
|
+
request: request,
|
327
|
+
input: input,
|
328
|
+
token: input[:token],
|
329
|
+
), ActionResult.new)
|
330
|
+
rescue HaveAPI::AuthenticationError => e
|
331
|
+
error(e.message)
|
332
|
+
end
|
333
|
+
|
334
|
+
unless result.ok?
|
335
|
+
error(result.error || 'authentication failed')
|
336
|
+
end
|
337
|
+
|
338
|
+
{
|
339
|
+
token: result.token,
|
340
|
+
valid_to: result.valid_to,
|
341
|
+
complete: result.complete?,
|
342
|
+
next_action: result.next_action,
|
343
|
+
}
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
170
348
|
end
|
171
349
|
end
|
172
350
|
end
|
@@ -12,7 +12,7 @@ module HaveAPI::ClientExamples
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def auth(method, desc)
|
15
|
-
login = {
|
15
|
+
login = {user: 'user', password: 'password', lifetime: 'fixed'}
|
16
16
|
|
17
17
|
case method
|
18
18
|
when :basic
|
@@ -50,8 +50,8 @@ END
|
|
50
50
|
base_url,
|
51
51
|
resolve_path(
|
52
52
|
action[:method],
|
53
|
-
action[:
|
54
|
-
sample[:
|
53
|
+
action[:path],
|
54
|
+
sample[:path_params] || [],
|
55
55
|
sample[:request]
|
56
56
|
)
|
57
57
|
)
|
@@ -43,13 +43,13 @@ END
|
|
43
43
|
path = [mountpoint].concat(resource_path)
|
44
44
|
|
45
45
|
unless class_action?
|
46
|
-
if !sample[:
|
46
|
+
if !sample[:path_params] || sample[:path_params].empty?
|
47
47
|
fail "example {#{sample}} of action #{resource_path.join('.')}"+
|
48
48
|
".#{action_name} is for an instance action but does not include "+
|
49
49
|
"URL parameters"
|
50
50
|
end
|
51
51
|
|
52
|
-
path << sample[:
|
52
|
+
path << sample[:path_params].first.to_s
|
53
53
|
end
|
54
54
|
|
55
55
|
path << 'actions' << action_name
|
@@ -112,7 +112,7 @@ END
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def class_action?
|
115
|
-
action[:
|
115
|
+
action[:path].index(/:[a-zA-Z\-_]+/).nil?
|
116
116
|
end
|
117
117
|
end
|
118
118
|
end
|
@@ -33,7 +33,7 @@ POST /_auth/token/tokens HTTP/1.1
|
|
33
33
|
Host: #{host}
|
34
34
|
Content-Type: application/json
|
35
35
|
|
36
|
-
#{JSON.pretty_generate({token: {
|
36
|
+
#{JSON.pretty_generate({token: {user: 'user', password: 'secret', lifetime: 'fixed'}})}
|
37
37
|
END
|
38
38
|
end
|
39
39
|
end
|
@@ -41,8 +41,8 @@ END
|
|
41
41
|
def request(sample)
|
42
42
|
path = resolve_path(
|
43
43
|
action[:method],
|
44
|
-
action[:
|
45
|
-
sample[:
|
44
|
+
action[:path],
|
45
|
+
sample[:path_params] || [],
|
46
46
|
sample[:request]
|
47
47
|
)
|
48
48
|
|
@@ -74,11 +74,11 @@ END
|
|
74
74
|
res
|
75
75
|
end
|
76
76
|
|
77
|
-
def resolve_path(method,
|
78
|
-
ret =
|
77
|
+
def resolve_path(method, path, path_params, input_params)
|
78
|
+
ret = path.clone
|
79
79
|
|
80
|
-
|
81
|
-
ret.sub!(
|
80
|
+
path_params.each do |v|
|
81
|
+
ret.sub!(/\{[a-zA-Z\-_]+\}/, v.to_s)
|
82
82
|
end
|
83
83
|
|
84
84
|
return ret if method != 'GET' || !input_params || input_params.empty?
|
@@ -56,7 +56,7 @@ END
|
|
56
56
|
def example(sample)
|
57
57
|
args = []
|
58
58
|
|
59
|
-
args.concat(sample[:
|
59
|
+
args.concat(sample[:path_params]) if sample[:path_params]
|
60
60
|
|
61
61
|
if sample[:request] && !sample[:request].empty?
|
62
62
|
args << JSON.pretty_generate(sample[:request])
|
@@ -39,7 +39,7 @@ END
|
|
39
39
|
def example(sample)
|
40
40
|
args = []
|
41
41
|
|
42
|
-
args.concat(sample[:
|
42
|
+
args.concat(sample[:path_params]) if sample[:path_params]
|
43
43
|
|
44
44
|
if sample[:request] && !sample[:request].empty?
|
45
45
|
args << format_parameters(:input, sample[:request])
|
@@ -47,7 +47,7 @@ END
|
|
47
47
|
cmd = [init]
|
48
48
|
cmd << resource_path.join('.')
|
49
49
|
cmd << action_name
|
50
|
-
cmd.concat(sample[:
|
50
|
+
cmd.concat(sample[:path_params]) if sample[:path_params]
|
51
51
|
|
52
52
|
if sample[:request] && !sample[:request].empty?
|
53
53
|
cmd << "-- \\\n"
|
@@ -42,7 +42,7 @@ END
|
|
42
42
|
def example(sample)
|
43
43
|
args = []
|
44
44
|
|
45
|
-
args.concat(sample[:
|
45
|
+
args.concat(sample[:path_params]) if sample[:path_params]
|
46
46
|
|
47
47
|
if sample[:request] && !sample[:request].empty?
|
48
48
|
args << PP.pp(sample[:request], '').strip
|
data/lib/haveapi/context.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
module HaveAPI
|
2
2
|
class Context
|
3
|
-
attr_accessor :server, :version, :request, :resource, :action, :
|
3
|
+
attr_accessor :server, :version, :request, :resource, :action, :path, :args,
|
4
4
|
:params, :current_user, :authorization, :endpoint,
|
5
5
|
:action_instance, :action_prepare, :layout
|
6
6
|
|
7
7
|
def initialize(server, version: nil, request: nil, resource: [], action: nil,
|
8
|
-
|
8
|
+
path: nil, args: nil, params: nil, user: nil,
|
9
9
|
authorization: nil, endpoint: nil)
|
10
10
|
@server = server
|
11
11
|
@version = version
|
12
12
|
@request = request
|
13
13
|
@resource = resource
|
14
14
|
@action = action
|
15
|
-
@
|
15
|
+
@path = path
|
16
16
|
@args = args
|
17
17
|
@params = params
|
18
18
|
@current_user = user
|
@@ -20,10 +20,10 @@ module HaveAPI
|
|
20
20
|
@endpoint = endpoint
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
return @
|
23
|
+
def resolved_path
|
24
|
+
return @path unless @args
|
25
25
|
|
26
|
-
ret = @
|
26
|
+
ret = @path.dup
|
27
27
|
|
28
28
|
@args.each do |arg|
|
29
29
|
resolve_arg!(ret, arg)
|
@@ -32,7 +32,7 @@ module HaveAPI
|
|
32
32
|
ret
|
33
33
|
end
|
34
34
|
|
35
|
-
def
|
35
|
+
def path_for(action, args=nil)
|
36
36
|
top_module = Kernel
|
37
37
|
top_route = @server.routes[@version]
|
38
38
|
|
@@ -60,20 +60,20 @@ module HaveAPI
|
|
60
60
|
ret
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
64
|
-
ret = params && action.
|
63
|
+
def call_path_params(action, obj)
|
64
|
+
ret = params && action.resolve_path_params(obj)
|
65
65
|
|
66
66
|
return [ret] if ret && !ret.is_a?(Array)
|
67
67
|
ret
|
68
68
|
end
|
69
69
|
|
70
|
-
def
|
71
|
-
|
70
|
+
def path_with_params(action, obj)
|
71
|
+
path_for(action, call_path_params(action, obj))
|
72
72
|
end
|
73
73
|
|
74
74
|
private
|
75
|
-
def resolve_arg!(
|
76
|
-
|
75
|
+
def resolve_arg!(path, arg)
|
76
|
+
path.sub!(/\{[a-zA-Z\-_]+\}/, arg.to_s)
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
data/lib/haveapi/example.rb
CHANGED
@@ -8,8 +8,8 @@ module HaveAPI
|
|
8
8
|
@authorization = block
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
@
|
11
|
+
def path_params(*params)
|
12
|
+
@path_params = params
|
13
13
|
end
|
14
14
|
|
15
15
|
def request(f)
|
@@ -60,7 +60,7 @@ module HaveAPI
|
|
60
60
|
{
|
61
61
|
title: @title,
|
62
62
|
comment: @comment,
|
63
|
-
|
63
|
+
path_params: @path_params,
|
64
64
|
request: filter_input_params(context, @request),
|
65
65
|
response: filter_output_params(context, @response),
|
66
66
|
status: @status.nil? ? true : @status,
|
data/lib/haveapi/exceptions.rb
CHANGED
@@ -58,6 +58,13 @@ module HaveAPI::Extensions
|
|
58
58
|
|
59
59
|
env = context.request.env
|
60
60
|
|
61
|
+
user =
|
62
|
+
if context.current_user.respond_to?(:id)
|
63
|
+
context.current_user.id
|
64
|
+
else
|
65
|
+
context.current_user
|
66
|
+
end
|
67
|
+
|
61
68
|
mail(context, exception, TEMPLATE.result(binding))
|
62
69
|
end
|
63
70
|
|
@@ -222,7 +229,7 @@ module HaveAPI::Extensions
|
|
222
229
|
</tr>
|
223
230
|
<tr>
|
224
231
|
<th>User</th>
|
225
|
-
<td><%=h
|
232
|
+
<td><%=h user %></td>
|
226
233
|
</tr>
|
227
234
|
</table>
|
228
235
|
<div class="clear"></div>
|