haveapi 0.12.1 → 0.13.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/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>
|