api-auth 1.5.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +10 -44
- data/.rubocop.yml +102 -0
- data/.travis.yml +1 -0
- data/Appraisals +8 -0
- data/CHANGELOG.md +8 -1
- data/Gemfile +3 -0
- data/README.md +33 -5
- data/VERSION +1 -1
- data/api_auth.gemspec +17 -17
- data/gemfiles/rails_23.gemfile +3 -0
- data/gemfiles/rails_30.gemfile +3 -0
- data/gemfiles/rails_31.gemfile +5 -0
- data/gemfiles/rails_32.gemfile +5 -0
- data/gemfiles/rails_4.gemfile +2 -0
- data/gemfiles/rails_41.gemfile +2 -0
- data/gemfiles/rails_42.gemfile +2 -0
- data/lib/api-auth.rb +1 -1
- data/lib/api_auth/base.rb +21 -25
- data/lib/api_auth/errors.rb +4 -3
- data/lib/api_auth/headers.rb +11 -27
- data/lib/api_auth/helpers.rb +2 -6
- data/lib/api_auth/railtie.rb +5 -50
- data/lib/api_auth/request_drivers/action_controller.rb +7 -13
- data/lib/api_auth/request_drivers/action_dispatch.rb +0 -6
- data/lib/api_auth/request_drivers/curb.rb +8 -14
- data/lib/api_auth/request_drivers/faraday.rb +11 -21
- data/lib/api_auth/request_drivers/httpi.rb +8 -14
- data/lib/api_auth/request_drivers/net_http.rb +8 -14
- data/lib/api_auth/request_drivers/rack.rb +10 -16
- data/lib/api_auth/request_drivers/rest_client.rb +9 -15
- data/spec/api_auth_spec.rb +90 -88
- data/spec/headers_spec.rb +69 -84
- data/spec/helpers_spec.rb +7 -9
- data/spec/railtie_spec.rb +42 -72
- data/spec/request_drivers/action_controller_spec.rb +53 -55
- data/spec/request_drivers/action_dispatch_spec.rb +52 -55
- data/spec/request_drivers/curb_spec.rb +25 -28
- data/spec/request_drivers/faraday_spec.rb +54 -56
- data/spec/request_drivers/httpi_spec.rb +42 -48
- data/spec/request_drivers/net_http_spec.rb +51 -53
- data/spec/request_drivers/rack_spec.rb +58 -60
- data/spec/request_drivers/rest_client_spec.rb +86 -89
- data/spec/spec_helper.rb +9 -9
- metadata +4 -11
- data/Gemfile.lock +0 -115
- data/gemfiles/rails_23.gemfile.lock +0 -70
- data/gemfiles/rails_30.gemfile.lock +0 -92
- data/gemfiles/rails_31.gemfile.lock +0 -98
- data/gemfiles/rails_32.gemfile.lock +0 -97
- data/gemfiles/rails_4.gemfile.lock +0 -94
- data/gemfiles/rails_41.gemfile.lock +0 -98
- data/gemfiles/rails_42.gemfile.lock +0 -115
data/spec/headers_spec.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
3
|
describe ApiAuth::Headers do
|
4
|
-
|
5
4
|
describe '#canonical_string' do
|
6
|
-
context
|
5
|
+
context 'uri edge cases' do
|
7
6
|
let(:request) { RestClient::Request.new(:url => uri, :method => :get) }
|
8
7
|
subject(:headers) { described_class.new(request) }
|
9
8
|
let(:uri) { '' }
|
@@ -12,7 +11,7 @@ describe ApiAuth::Headers do
|
|
12
11
|
let(:uri) { ''.freeze }
|
13
12
|
|
14
13
|
it 'adds / to canonical string' do
|
15
|
-
expect(subject.canonical_string).to eq('
|
14
|
+
expect(subject.canonical_string).to eq('GET,,,/,')
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
@@ -20,7 +19,7 @@ describe ApiAuth::Headers do
|
|
20
19
|
let(:uri) { 'http://google.com'.freeze }
|
21
20
|
|
22
21
|
it 'return / as canonical string path' do
|
23
|
-
expect(subject.canonical_string).to eq('
|
22
|
+
expect(subject.canonical_string).to eq('GET,,,/,')
|
24
23
|
end
|
25
24
|
|
26
25
|
it 'does not change request url (by removing host)' do
|
@@ -32,7 +31,7 @@ describe ApiAuth::Headers do
|
|
32
31
|
let(:uri) { 'http://google.com/'.freeze }
|
33
32
|
|
34
33
|
it 'return / as canonical string path' do
|
35
|
-
expect(subject.canonical_string).to eq('
|
34
|
+
expect(subject.canonical_string).to eq('GET,,,/,')
|
36
35
|
end
|
37
36
|
|
38
37
|
it 'does not change request url (by removing host)' do
|
@@ -41,107 +40,93 @@ describe ApiAuth::Headers do
|
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
44
|
-
context
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
describe "#canonical_string_with_http_method" do
|
60
|
-
context "with a driver that supplies http_method" do
|
61
|
-
let(:request){ RestClient::Request.new(:url => "http://google.com", :method => :get) }
|
62
|
-
subject(:headers) { described_class.new(request) }
|
63
|
-
let(:driver){ headers.instance_variable_get("@request")}
|
64
|
-
|
65
|
-
before do
|
66
|
-
allow(driver).to receive(:http_method).and_return "GET"
|
67
|
-
allow(driver).to receive(:content_type).and_return "text/html"
|
68
|
-
allow(driver).to receive(:content_md5).and_return "12345"
|
69
|
-
allow(driver).to receive(:request_uri).and_return "/foo"
|
70
|
-
allow(driver).to receive(:timestamp).and_return "Mon, 23 Jan 1984 03:29:56 GMT"
|
71
|
-
end
|
43
|
+
context 'string construction' do
|
44
|
+
context 'with a driver that supplies http_method' do
|
45
|
+
let(:request) { RestClient::Request.new(:url => 'http://google.com', :method => :get) }
|
46
|
+
subject(:headers) { described_class.new(request) }
|
47
|
+
let(:driver) { headers.instance_variable_get('@request') }
|
48
|
+
|
49
|
+
before do
|
50
|
+
allow(driver).to receive(:http_method).and_return 'GET'
|
51
|
+
allow(driver).to receive(:content_type).and_return 'text/html'
|
52
|
+
allow(driver).to receive(:content_md5).and_return '12345'
|
53
|
+
allow(driver).to receive(:request_uri).and_return '/foo'
|
54
|
+
allow(driver).to receive(:timestamp).and_return 'Mon, 23 Jan 1984 03:29:56 GMT'
|
55
|
+
end
|
72
56
|
|
73
|
-
|
74
|
-
|
75
|
-
|
57
|
+
context 'when not passed an override' do
|
58
|
+
it "constructs the canonical_string with the driver's http method" do
|
59
|
+
expect(headers.canonical_string).to eq 'GET,text/html,12345,/foo,Mon, 23 Jan 1984 03:29:56 GMT'
|
60
|
+
end
|
76
61
|
end
|
77
|
-
end
|
78
62
|
|
79
|
-
|
80
|
-
|
81
|
-
|
63
|
+
context 'when passed an override' do
|
64
|
+
it 'constructs the canonical_string with the overridden http method' do
|
65
|
+
expect(headers.canonical_string('put')).to eq 'PUT,text/html,12345,/foo,Mon, 23 Jan 1984 03:29:56 GMT'
|
66
|
+
end
|
82
67
|
end
|
83
68
|
end
|
84
|
-
end
|
85
69
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
70
|
+
context "when a driver that doesn't supply http_method" do
|
71
|
+
let(:request) do
|
72
|
+
Curl::Easy.new('/resource.xml?foo=bar&bar=foo') do |curl|
|
73
|
+
curl.headers = { 'Content-Type' => 'text/plain' }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
subject(:headers) { described_class.new(request) }
|
77
|
+
let(:driver) { headers.instance_variable_get('@request') }
|
78
|
+
|
79
|
+
before do
|
80
|
+
allow(driver).to receive(:http_method).and_return nil
|
81
|
+
allow(driver).to receive(:content_type).and_return 'text/html'
|
82
|
+
allow(driver).to receive(:content_md5).and_return '12345'
|
83
|
+
allow(driver).to receive(:request_uri).and_return '/foo'
|
84
|
+
allow(driver).to receive(:timestamp).and_return 'Mon, 23 Jan 1984 03:29:56 GMT'
|
90
85
|
end
|
91
|
-
end
|
92
|
-
subject(:headers) { described_class.new(request) }
|
93
|
-
let(:driver){ headers.instance_variable_get("@request")}
|
94
|
-
|
95
|
-
before do
|
96
|
-
allow(driver).to receive(:http_method).and_return nil
|
97
|
-
allow(driver).to receive(:content_type).and_return "text/html"
|
98
|
-
allow(driver).to receive(:content_md5).and_return "12345"
|
99
|
-
allow(driver).to receive(:request_uri).and_return "/foo"
|
100
|
-
allow(driver).to receive(:timestamp).and_return "Mon, 23 Jan 1984 03:29:56 GMT"
|
101
|
-
end
|
102
86
|
|
103
|
-
|
104
|
-
|
105
|
-
|
87
|
+
context 'when not passed an override' do
|
88
|
+
it 'raises an error' do
|
89
|
+
expect { headers.canonical_string }.to raise_error(ArgumentError)
|
90
|
+
end
|
106
91
|
end
|
107
|
-
end
|
108
92
|
|
109
|
-
|
110
|
-
|
111
|
-
|
93
|
+
context 'when passed an override' do
|
94
|
+
it 'constructs the canonical_string with the overridden http method' do
|
95
|
+
expect(headers.canonical_string('put')).to eq 'PUT,text/html,12345,/foo,Mon, 23 Jan 1984 03:29:56 GMT'
|
96
|
+
end
|
112
97
|
end
|
113
98
|
end
|
114
99
|
end
|
115
100
|
end
|
116
101
|
|
117
102
|
describe '#calculate_md5' do
|
118
|
-
subject(:headers){ described_class.new(request) }
|
119
|
-
let(:driver){ headers.instance_variable_get(
|
103
|
+
subject(:headers) { described_class.new(request) }
|
104
|
+
let(:driver) { headers.instance_variable_get('@request') }
|
120
105
|
|
121
|
-
context
|
122
|
-
let(:request)
|
106
|
+
context 'no md5 already calculated' do
|
107
|
+
let(:request) do
|
123
108
|
RestClient::Request.new(
|
124
109
|
:url => 'http://google.com',
|
125
110
|
:method => :post,
|
126
111
|
:payload => "hello\nworld"
|
127
112
|
)
|
128
|
-
|
113
|
+
end
|
129
114
|
|
130
|
-
it
|
115
|
+
it 'populates the md5 header' do
|
131
116
|
expect(driver).to receive(:populate_content_md5)
|
132
117
|
headers.calculate_md5
|
133
118
|
end
|
134
119
|
end
|
135
120
|
|
136
|
-
context
|
137
|
-
let(:request)
|
121
|
+
context 'md5 already calculated' do
|
122
|
+
let(:request) do
|
138
123
|
RestClient::Request.new(
|
139
124
|
:url => 'http://google.com',
|
140
125
|
:method => :post,
|
141
126
|
:payload => "hello\nworld",
|
142
|
-
:headers => {:content_md5 =>
|
127
|
+
:headers => { :content_md5 => 'abcd' }
|
143
128
|
)
|
144
|
-
|
129
|
+
end
|
145
130
|
|
146
131
|
it "doesn't populate the md5 header" do
|
147
132
|
expect(driver).not_to receive(:populate_content_md5)
|
@@ -150,30 +135,30 @@ describe ApiAuth::Headers do
|
|
150
135
|
end
|
151
136
|
end
|
152
137
|
|
153
|
-
describe
|
154
|
-
let(:request){ RestClient::Request.new(:url =>
|
155
|
-
subject(:headers){ described_class.new(request) }
|
156
|
-
let(:driver){ headers.instance_variable_get(
|
138
|
+
describe '#md5_mismatch?' do
|
139
|
+
let(:request) { RestClient::Request.new(:url => 'http://google.com', :method => :get) }
|
140
|
+
subject(:headers) { described_class.new(request) }
|
141
|
+
let(:driver) { headers.instance_variable_get('@request') }
|
157
142
|
|
158
|
-
context
|
159
|
-
it
|
160
|
-
allow(driver).to receive(:content_md5).and_return
|
143
|
+
context 'when request has md5 header' do
|
144
|
+
it 'asks the driver' do
|
145
|
+
allow(driver).to receive(:content_md5).and_return '1234'
|
161
146
|
|
162
147
|
expect(driver).to receive(:md5_mismatch?).and_call_original
|
163
148
|
headers.md5_mismatch?
|
164
149
|
end
|
165
150
|
end
|
166
151
|
|
167
|
-
context
|
152
|
+
context 'when request has no md5' do
|
168
153
|
it "doesn't ask the driver" do
|
169
|
-
allow(driver).to receive(:content_md5).and_return
|
154
|
+
allow(driver).to receive(:content_md5).and_return ''
|
170
155
|
|
171
156
|
expect(driver).not_to receive(:md5_mismatch?).and_call_original
|
172
157
|
headers.md5_mismatch?
|
173
158
|
end
|
174
159
|
|
175
|
-
it
|
176
|
-
allow(driver).to receive(:content_md5).and_return
|
160
|
+
it 'returns false' do
|
161
|
+
allow(driver).to receive(:content_md5).and_return ''
|
177
162
|
|
178
163
|
expect(headers.md5_mismatch?).to be false
|
179
164
|
end
|
data/spec/helpers_spec.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
|
6
|
-
expect(ApiAuth.b64_encode("some string")).not_to match(/\n/)
|
3
|
+
describe 'ApiAuth::Helpers' do
|
4
|
+
it 'should strip the new line character on a Base64 encoding' do
|
5
|
+
expect(ApiAuth.b64_encode('some string')).not_to match(/\n/)
|
7
6
|
end
|
8
|
-
|
7
|
+
|
9
8
|
it "should properly upcase a hash's keys" do
|
10
|
-
hsh = {
|
11
|
-
expect(ApiAuth.capitalize_keys(hsh)[
|
9
|
+
hsh = { 'JoE' => 'rOOLz' }
|
10
|
+
expect(ApiAuth.capitalize_keys(hsh)['JOE']).to eq('rOOLz')
|
12
11
|
end
|
13
|
-
|
14
|
-
end
|
12
|
+
end
|
data/spec/railtie_spec.rb
CHANGED
@@ -1,14 +1,11 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
2
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
API_KEY_STORE = { "1044" => "l16imAXie1sRMcJODpOG7UwC1VyoqvO13jejkfpKWX4Z09W8DC9IrU23DvCwMry7pgSFW6c5S1GIfV0OY6F/vUA==" }
|
6
|
-
|
7
|
-
describe "Rails controller integration" do
|
3
|
+
describe 'Rails integration' do
|
4
|
+
API_KEY_STORE = { '1044' => 'l16imAXie1sRMcJODpOG7UwC1VyoqvO13jejkfpKWX4Z09W8DC9IrU23DvCwMry7pgSFW6c5S1GIfV0OY6F/vUA==' }.freeze
|
8
5
|
|
6
|
+
describe 'Rails controller integration' do
|
9
7
|
class ApplicationController < ActionController::Base
|
10
|
-
|
11
|
-
private
|
8
|
+
private
|
12
9
|
|
13
10
|
def require_api_auth
|
14
11
|
if (access_id = get_api_access_id_from_request)
|
@@ -16,12 +13,11 @@ describe "Rails integration" do
|
|
16
13
|
end
|
17
14
|
|
18
15
|
respond_to do |format|
|
19
|
-
format.xml { render :xml =>
|
20
|
-
format.json { render :json =>
|
21
|
-
format.html { render :text =>
|
16
|
+
format.xml { render :xml => 'You are unauthorized to perform this action.', :status => 401 }
|
17
|
+
format.json { render :json => 'You are unauthorized to perform this action.', :status => 401 }
|
18
|
+
format.html { render :text => 'You are unauthorized to perform this action', :status => 401 }
|
22
19
|
end
|
23
20
|
end
|
24
|
-
|
25
21
|
end
|
26
22
|
|
27
23
|
class TestController < ApplicationController
|
@@ -34,18 +30,20 @@ describe "Rails integration" do
|
|
34
30
|
end
|
35
31
|
|
36
32
|
def index
|
37
|
-
render :text =>
|
33
|
+
render :text => 'OK'
|
38
34
|
end
|
39
35
|
|
40
36
|
def public
|
41
|
-
render :text =>
|
37
|
+
render :text => 'OK'
|
42
38
|
end
|
43
39
|
|
44
|
-
def rescue_action(e)
|
40
|
+
def rescue_action(e)
|
41
|
+
raise(e)
|
42
|
+
end
|
45
43
|
end
|
46
44
|
|
47
45
|
unless defined?(ActionDispatch)
|
48
|
-
ActionController::Routing::Routes.draw {|map| map.resources :test }
|
46
|
+
ActionController::Routing::Routes.draw { |map| map.resources :test }
|
49
47
|
end
|
50
48
|
|
51
49
|
def generated_response(request, action = :index)
|
@@ -58,102 +56,74 @@ describe "Rails integration" do
|
|
58
56
|
response
|
59
57
|
else
|
60
58
|
request.action = action.to_s
|
61
|
-
request.path = "/#{action
|
59
|
+
request.path = "/#{action}"
|
62
60
|
TestController.new.process(request, ActionController::TestResponse.new)
|
63
61
|
end
|
64
62
|
end
|
65
63
|
|
66
|
-
it
|
64
|
+
it 'should permit a request with properly signed headers' do
|
67
65
|
request = ActionController::TestRequest.new
|
68
66
|
request.env['DATE'] = Time.now.utc.httpdate
|
69
|
-
ApiAuth.sign!(request,
|
67
|
+
ApiAuth.sign!(request, '1044', API_KEY_STORE['1044'])
|
70
68
|
response = generated_response(request, :index)
|
71
|
-
expect(response.code).to eq(
|
69
|
+
expect(response.code).to eq('200')
|
72
70
|
end
|
73
71
|
|
74
|
-
it
|
72
|
+
it 'should forbid a request with properly signed headers but timestamp > 15 minutes' do
|
75
73
|
request = ActionController::TestRequest.new
|
76
|
-
request.env['DATE'] =
|
77
|
-
ApiAuth.sign!(request,
|
74
|
+
request.env['DATE'] = 'Mon, 23 Jan 1984 03:29:56 GMT'
|
75
|
+
ApiAuth.sign!(request, '1044', API_KEY_STORE['1044'])
|
78
76
|
response = generated_response(request, :index)
|
79
|
-
expect(response.code).to eq(
|
77
|
+
expect(response.code).to eq('401')
|
80
78
|
end
|
81
79
|
|
82
80
|
it "should insert a DATE header in the request when one hasn't been specified" do
|
83
81
|
request = ActionController::TestRequest.new
|
84
|
-
ApiAuth.sign!(request,
|
82
|
+
ApiAuth.sign!(request, '1044', API_KEY_STORE['1044'])
|
85
83
|
expect(request.headers['DATE']).not_to be_nil
|
86
84
|
end
|
87
85
|
|
88
|
-
it
|
86
|
+
it 'should forbid an unsigned request to a protected controller action' do
|
89
87
|
request = ActionController::TestRequest.new
|
90
88
|
response = generated_response(request, :index)
|
91
|
-
expect(response.code).to eq(
|
89
|
+
expect(response.code).to eq('401')
|
92
90
|
end
|
93
91
|
|
94
|
-
it
|
92
|
+
it 'should forbid a request with a bogus signature' do
|
95
93
|
request = ActionController::TestRequest.new
|
96
|
-
request.env['Authorization'] =
|
94
|
+
request.env['Authorization'] = 'APIAuth bogus:bogus'
|
97
95
|
response = generated_response(request, :index)
|
98
|
-
expect(response.code).to eq(
|
96
|
+
expect(response.code).to eq('401')
|
99
97
|
end
|
100
98
|
|
101
|
-
it
|
99
|
+
it 'should allow non-protected controller actions to function as before' do
|
102
100
|
request = ActionController::TestRequest.new
|
103
101
|
response = generated_response(request, :public)
|
104
|
-
expect(response.code).to eq(
|
102
|
+
expect(response.code).to eq('200')
|
105
103
|
end
|
106
|
-
|
107
104
|
end
|
108
105
|
|
109
|
-
describe
|
110
|
-
|
106
|
+
describe 'Rails ActiveResource integration' do
|
111
107
|
class TestResource < ActiveResource::Base
|
112
|
-
with_api_auth
|
113
|
-
self.site =
|
108
|
+
with_api_auth '1044', API_KEY_STORE['1044']
|
109
|
+
self.site = 'http://localhost/'
|
114
110
|
self.format = :xml
|
115
111
|
end
|
116
112
|
|
117
|
-
it
|
118
|
-
timestamp = Time.parse(
|
113
|
+
it 'should send signed requests automagically' do
|
114
|
+
timestamp = Time.parse('Mon, 23 Jan 1984 03:29:56 GMT')
|
119
115
|
allow(Time).to receive(:now).at_least(1).times.and_return(timestamp)
|
120
116
|
ActiveResource::HttpMock.respond_to do |mock|
|
121
|
-
mock.get
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
117
|
+
mock.get '/test_resources/1.xml',
|
118
|
+
{
|
119
|
+
'Authorization' => 'APIAuth 1044:LZ1jujf3x1nnGR70/208WPXdUHw=',
|
120
|
+
'Accept' => 'application/xml',
|
121
|
+
'DATE' => 'Mon, 23 Jan 1984 03:29:56 GMT'
|
122
|
+
},
|
123
|
+
{ :id => '1' }.to_xml(:root => 'test_resource')
|
128
124
|
end
|
129
|
-
expect(ApiAuth).to receive(:sign!).with(anything,
|
125
|
+
expect(ApiAuth).to receive(:sign!).with(anything, '1044', API_KEY_STORE['1044']).and_call_original
|
130
126
|
TestResource.find(1)
|
131
127
|
end
|
132
|
-
|
133
|
-
context "when telling it to include the http method in the signature" do
|
134
|
-
class TestResourceForHttpMethod < ActiveResource::Base
|
135
|
-
with_api_auth "1044", API_KEY_STORE["1044"], :sign_with_http_method => true
|
136
|
-
self.site = "http://localhost/"
|
137
|
-
self.format = :xml
|
138
|
-
end
|
139
|
-
|
140
|
-
it "signs with the http method" do
|
141
|
-
timestamp = Time.parse("Mon, 23 Jan 1984 03:29:56 GMT")
|
142
|
-
allow(Time).to receive(:now).at_least(1).times.and_return(timestamp)
|
143
|
-
ActiveResource::HttpMock.respond_to do |mock|
|
144
|
-
mock.get "/test_resource_for_http_methods/1.xml",
|
145
|
-
{
|
146
|
-
'Authorization' => 'APIAuth 1044:Gq190cxgKm7oWH1fc+y8+wmD9ts=',
|
147
|
-
'Accept' => 'application/xml',
|
148
|
-
'DATE' => "Mon, 23 Jan 1984 03:29:56 GMT"
|
149
|
-
},
|
150
|
-
{ :id => "1" }.to_xml(:root => 'test_resource')
|
151
|
-
end
|
152
|
-
expect(ApiAuth).to receive(:sign!).with(anything, "1044", API_KEY_STORE["1044"], { :with_http_method => true }).and_call_original
|
153
|
-
TestResourceForHttpMethod.find(1)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
128
|
end
|
158
|
-
|
159
129
|
end
|