@@ -28,6 +28,79 class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base | |||
|
28 | 28 | Setting.rest_api_enabled = '0' |
|
29 | 29 | end |
|
30 | 30 | |
|
31 | def test_api_should_deny_without_credentials | |
|
32 | get '/users/current.xml', {} | |
|
33 | assert_response 401 | |
|
34 | assert_equal User.anonymous, User.current | |
|
35 | assert response.headers.has_key?('WWW-Authenticate') | |
|
36 | end | |
|
37 | ||
|
38 | def test_api_should_accept_http_basic_auth_using_username_and_password | |
|
39 | user = User.generate! do |user| | |
|
40 | user.password = 'my_password' | |
|
41 | end | |
|
42 | get '/users/current.xml', {}, credentials(user.login, 'my_password') | |
|
43 | assert_response 200 | |
|
44 | assert_equal user, User.current | |
|
45 | end | |
|
46 | ||
|
47 | def test_api_should_deny_http_basic_auth_using_username_and_wrong_password | |
|
48 | user = User.generate! do |user| | |
|
49 | user.password = 'my_password' | |
|
50 | end | |
|
51 | get '/users/current.xml', {}, credentials(user.login, 'wrong_password') | |
|
52 | assert_response 401 | |
|
53 | assert_equal User.anonymous, User.current | |
|
54 | end | |
|
55 | ||
|
56 | def test_api_should_accept_http_basic_auth_using_api_key | |
|
57 | user = User.generate! | |
|
58 | token = Token.create!(:user => user, :action => 'api') | |
|
59 | get '/users/current.xml', {}, credentials(token.value, 'X') | |
|
60 | assert_response 200 | |
|
61 | assert_equal user, User.current | |
|
62 | end | |
|
63 | ||
|
64 | def test_api_should_deny_http_basic_auth_using_wrong_api_key | |
|
65 | user = User.generate! | |
|
66 | token = Token.create!(:user => user, :action => 'feeds') # not the API key | |
|
67 | get '/users/current.xml', {}, credentials(token.value, 'X') | |
|
68 | assert_response 401 | |
|
69 | assert_equal User.anonymous, User.current | |
|
70 | end | |
|
71 | ||
|
72 | def test_api_should_accept_auth_using_api_key_as_parameter | |
|
73 | user = User.generate! | |
|
74 | token = Token.create!(:user => user, :action => 'api') | |
|
75 | get "/users/current.xml?key=#{token.value}", {} | |
|
76 | assert_response 200 | |
|
77 | assert_equal user, User.current | |
|
78 | end | |
|
79 | ||
|
80 | def test_api_should_deny_auth_using_wrong_api_key_as_parameter | |
|
81 | user = User.generate! | |
|
82 | token = Token.create!(:user => user, :action => 'feeds') # not the API key | |
|
83 | get "/users/current.xml?key=#{token.value}", {} | |
|
84 | assert_response 401 | |
|
85 | assert_equal User.anonymous, User.current | |
|
86 | end | |
|
87 | ||
|
88 | def test_api_should_accept_auth_using_api_key_as_request_header | |
|
89 | user = User.generate! | |
|
90 | token = Token.create!(:user => user, :action => 'api') | |
|
91 | get "/users/current.xml", {}, {'X-Redmine-API-Key' => token.value.to_s} | |
|
92 | assert_response 200 | |
|
93 | assert_equal user, User.current | |
|
94 | end | |
|
95 | ||
|
96 | def test_api_should_deny_auth_using_wrong_api_key_as_request_header | |
|
97 | user = User.generate! | |
|
98 | token = Token.create!(:user => user, :action => 'feeds') # not the API key | |
|
99 | get "/users/current.xml", {}, {'X-Redmine-API-Key' => token.value.to_s} | |
|
100 | assert_response 401 | |
|
101 | assert_equal User.anonymous, User.current | |
|
102 | end | |
|
103 | ||
|
31 | 104 | def test_api_should_trigger_basic_http_auth_with_basic_authorization_header |
|
32 | 105 | ApplicationController.any_instance.expects(:authenticate_with_http_basic).once |
|
33 | 106 | get '/users/current.xml', {}, credentials('jsmith') |
@@ -48,44 +48,6 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base | |||
|
48 | 48 | Setting.rest_api_enabled = '1' |
|
49 | 49 | end |
|
50 | 50 | |
|
51 | # Use a private project to make sure auth is really working and not just | |
|
52 | # only showing public issues. | |
|
53 | should_allow_api_authentication(:get, "/projects/private-child/issues.xml") | |
|
54 | should_allow_api_authentication(:get, "/projects/private-child/issues.json") | |
|
55 | ||
|
56 | should_allow_api_authentication(:get, "/issues/6.xml") | |
|
57 | should_allow_api_authentication(:get, "/issues/6.json") | |
|
58 | ||
|
59 | should_allow_api_authentication( | |
|
60 | :post, | |
|
61 | '/issues.xml', | |
|
62 | {:issue => {:project_id => 1, :subject => 'API test', :tracker_id => 2, :status_id => 3}}, | |
|
63 | {:success_code => :created} | |
|
64 | ) | |
|
65 | should_allow_api_authentication(:post, | |
|
66 | '/issues.json', | |
|
67 | {:issue => {:project_id => 1, :subject => 'API test', | |
|
68 | :tracker_id => 2, :status_id => 3}}, | |
|
69 | {:success_code => :created}) | |
|
70 | ||
|
71 | should_allow_api_authentication(:put, | |
|
72 | '/issues/6.xml', | |
|
73 | {:issue => {:subject => 'API update', :notes => 'A new note'}}, | |
|
74 | {:success_code => :ok}) | |
|
75 | should_allow_api_authentication(:put, | |
|
76 | '/issues/6.json', | |
|
77 | {:issue => {:subject => 'API update', :notes => 'A new note'}}, | |
|
78 | {:success_code => :ok}) | |
|
79 | ||
|
80 | should_allow_api_authentication(:delete, | |
|
81 | '/issues/6.xml', | |
|
82 | {}, | |
|
83 | {:success_code => :ok}) | |
|
84 | should_allow_api_authentication(:delete, | |
|
85 | '/issues/6.json', | |
|
86 | {}, | |
|
87 | {:success_code => :ok}) | |
|
88 | ||
|
89 | 51 | test "GET /issues.xml should contain metadata" do |
|
90 | 52 | get '/issues.xml' |
|
91 | 53 | assert_select 'issues[type=array][total_count=?][limit="25"][offset="0"]', |
@@ -31,9 +31,6 class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base | |||
|
31 | 31 | Setting.rest_api_enabled = '1' |
|
32 | 32 | end |
|
33 | 33 | |
|
34 | should_allow_api_authentication(:get, "/projects/onlinestore/news.xml") | |
|
35 | should_allow_api_authentication(:get, "/projects/onlinestore/news.json") | |
|
36 | ||
|
37 | 34 | test "GET /news.xml should return news" do |
|
38 | 35 | get '/news.xml' |
|
39 | 36 |
@@ -27,23 +27,6 class Redmine::ApiTest::ProjectsTest < Redmine::ApiTest::Base | |||
|
27 | 27 | set_tmp_attachments_directory |
|
28 | 28 | end |
|
29 | 29 | |
|
30 | # TODO: A private project is needed because should_allow_api_authentication | |
|
31 | # actually tests that authentication is *required*, not just allowed | |
|
32 | should_allow_api_authentication(:get, "/projects/2.xml") | |
|
33 | should_allow_api_authentication(:get, "/projects/2.json") | |
|
34 | should_allow_api_authentication(:post, | |
|
35 | '/projects.xml', | |
|
36 | {:project => {:name => 'API test', :identifier => 'api-test'}}, | |
|
37 | {:success_code => :created}) | |
|
38 | should_allow_api_authentication(:put, | |
|
39 | '/projects/2.xml', | |
|
40 | {:project => {:name => 'API update'}}, | |
|
41 | {:success_code => :ok}) | |
|
42 | should_allow_api_authentication(:delete, | |
|
43 | '/projects/2.xml', | |
|
44 | {}, | |
|
45 | {:success_code => :ok}) | |
|
46 | ||
|
47 | 30 | test "GET /projects.xml should return projects" do |
|
48 | 31 | get '/projects.xml' |
|
49 | 32 | assert_response :success |
@@ -24,45 +24,6 class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base | |||
|
24 | 24 | Setting.rest_api_enabled = '1' |
|
25 | 25 | end |
|
26 | 26 | |
|
27 | should_allow_api_authentication(:get, "/users.xml") | |
|
28 | should_allow_api_authentication(:get, "/users.json") | |
|
29 | should_allow_api_authentication(:post, | |
|
30 | '/users.xml', | |
|
31 | {:user => { | |
|
32 | :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', | |
|
33 | :mail => 'foo@example.net', :password => 'secret123' | |
|
34 | }}, | |
|
35 | {:success_code => :created}) | |
|
36 | should_allow_api_authentication(:post, | |
|
37 | '/users.json', | |
|
38 | {:user => { | |
|
39 | :login => 'foo', :firstname => 'Firstname', :lastname => 'Lastname', | |
|
40 | :mail => 'foo@example.net' | |
|
41 | }}, | |
|
42 | {:success_code => :created}) | |
|
43 | should_allow_api_authentication(:put, | |
|
44 | '/users/2.xml', | |
|
45 | {:user => { | |
|
46 | :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', | |
|
47 | :mail => 'jsmith@somenet.foo' | |
|
48 | }}, | |
|
49 | {:success_code => :ok}) | |
|
50 | should_allow_api_authentication(:put, | |
|
51 | '/users/2.json', | |
|
52 | {:user => { | |
|
53 | :login => 'jsmith', :firstname => 'John', :lastname => 'Renamed', | |
|
54 | :mail => 'jsmith@somenet.foo' | |
|
55 | }}, | |
|
56 | {:success_code => :ok}) | |
|
57 | should_allow_api_authentication(:delete, | |
|
58 | '/users/2.xml', | |
|
59 | {}, | |
|
60 | {:success_code => :ok}) | |
|
61 | should_allow_api_authentication(:delete, | |
|
62 | '/users/2.xml', | |
|
63 | {}, | |
|
64 | {:success_code => :ok}) | |
|
65 | ||
|
66 | 27 | test "GET /users/:id.xml should return the user" do |
|
67 | 28 | get '/users/2.xml' |
|
68 | 29 |
@@ -263,255 +263,6 module Redmine | |||
|
263 | 263 | |
|
264 | 264 | # Base class for API tests |
|
265 | 265 | class Base < ActionDispatch::IntegrationTest |
|
266 | # Test that a request allows the three types of API authentication | |
|
267 | # | |
|
268 | # * HTTP Basic with username and password | |
|
269 | # * HTTP Basic with an api key for the username | |
|
270 | # * Key based with the key=X parameter | |
|
271 | # | |
|
272 | # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) | |
|
273 | # @param [String] url the request url | |
|
274 | # @param [optional, Hash] parameters additional request parameters | |
|
275 | # @param [optional, Hash] options additional options | |
|
276 | # @option options [Symbol] :success_code Successful response code (:success) | |
|
277 | # @option options [Symbol] :failure_code Failure response code (:unauthorized) | |
|
278 | def self.should_allow_api_authentication(http_method, url, parameters={}, options={}) | |
|
279 | should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options) | |
|
280 | should_allow_http_basic_auth_with_key(http_method, url, parameters, options) | |
|
281 | should_allow_key_based_auth(http_method, url, parameters, options) | |
|
282 | end | |
|
283 | ||
|
284 | # Test that a request allows the username and password for HTTP BASIC | |
|
285 | # | |
|
286 | # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) | |
|
287 | # @param [String] url the request url | |
|
288 | # @param [optional, Hash] parameters additional request parameters | |
|
289 | # @param [optional, Hash] options additional options | |
|
290 | # @option options [Symbol] :success_code Successful response code (:success) | |
|
291 | # @option options [Symbol] :failure_code Failure response code (:unauthorized) | |
|
292 | def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={}) | |
|
293 | success_code = options[:success_code] || :success | |
|
294 | failure_code = options[:failure_code] || :unauthorized | |
|
295 | ||
|
296 | context "should allow http basic auth using a username and password for #{http_method} #{url}" do | |
|
297 | context "with a valid HTTP authentication" do | |
|
298 | setup do | |
|
299 | @user = User.generate! do |user| | |
|
300 | user.admin = true | |
|
301 | user.password = 'my_password' | |
|
302 | end | |
|
303 | send(http_method, url, parameters, credentials(@user.login, 'my_password')) | |
|
304 | end | |
|
305 | ||
|
306 | should_respond_with success_code | |
|
307 | should_respond_with_content_type_based_on_url(url) | |
|
308 | should "login as the user" do | |
|
309 | assert_equal @user, User.current | |
|
310 | end | |
|
311 | end | |
|
312 | ||
|
313 | context "with an invalid HTTP authentication" do | |
|
314 | setup do | |
|
315 | @user = User.generate! | |
|
316 | send(http_method, url, parameters, credentials(@user.login, 'wrong_password')) | |
|
317 | end | |
|
318 | ||
|
319 | should_respond_with failure_code | |
|
320 | should_respond_with_content_type_based_on_url(url) | |
|
321 | should "not login as the user" do | |
|
322 | assert_equal User.anonymous, User.current | |
|
323 | end | |
|
324 | end | |
|
325 | ||
|
326 | context "without credentials" do | |
|
327 | setup do | |
|
328 | send(http_method, url, parameters) | |
|
329 | end | |
|
330 | ||
|
331 | should_respond_with failure_code | |
|
332 | should_respond_with_content_type_based_on_url(url) | |
|
333 | should "include_www_authenticate_header" do | |
|
334 | assert @controller.response.headers.has_key?('WWW-Authenticate') | |
|
335 | end | |
|
336 | end | |
|
337 | end | |
|
338 | end | |
|
339 | ||
|
340 | # Test that a request allows the API key with HTTP BASIC | |
|
341 | # | |
|
342 | # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) | |
|
343 | # @param [String] url the request url | |
|
344 | # @param [optional, Hash] parameters additional request parameters | |
|
345 | # @param [optional, Hash] options additional options | |
|
346 | # @option options [Symbol] :success_code Successful response code (:success) | |
|
347 | # @option options [Symbol] :failure_code Failure response code (:unauthorized) | |
|
348 | def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={}) | |
|
349 | success_code = options[:success_code] || :success | |
|
350 | failure_code = options[:failure_code] || :unauthorized | |
|
351 | ||
|
352 | context "should allow http basic auth with a key for #{http_method} #{url}" do | |
|
353 | context "with a valid HTTP authentication using the API token" do | |
|
354 | setup do | |
|
355 | @user = User.generate! do |user| | |
|
356 | user.admin = true | |
|
357 | end | |
|
358 | @token = Token.create!(:user => @user, :action => 'api') | |
|
359 | send(http_method, url, parameters, credentials(@token.value, 'X')) | |
|
360 | end | |
|
361 | should_respond_with success_code | |
|
362 | should_respond_with_content_type_based_on_url(url) | |
|
363 | should_be_a_valid_response_string_based_on_url(url) | |
|
364 | should "login as the user" do | |
|
365 | assert_equal @user, User.current | |
|
366 | end | |
|
367 | end | |
|
368 | ||
|
369 | context "with an invalid HTTP authentication" do | |
|
370 | setup do | |
|
371 | @user = User.generate! | |
|
372 | @token = Token.create!(:user => @user, :action => 'feeds') | |
|
373 | send(http_method, url, parameters, credentials(@token.value, 'X')) | |
|
374 | end | |
|
375 | should_respond_with failure_code | |
|
376 | should_respond_with_content_type_based_on_url(url) | |
|
377 | should "not login as the user" do | |
|
378 | assert_equal User.anonymous, User.current | |
|
379 | end | |
|
380 | end | |
|
381 | end | |
|
382 | end | |
|
383 | ||
|
384 | # Test that a request allows full key authentication | |
|
385 | # | |
|
386 | # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete) | |
|
387 | # @param [String] url the request url, without the key=ZXY parameter | |
|
388 | # @param [optional, Hash] parameters additional request parameters | |
|
389 | # @param [optional, Hash] options additional options | |
|
390 | # @option options [Symbol] :success_code Successful response code (:success) | |
|
391 | # @option options [Symbol] :failure_code Failure response code (:unauthorized) | |
|
392 | def self.should_allow_key_based_auth(http_method, url, parameters={}, options={}) | |
|
393 | success_code = options[:success_code] || :success | |
|
394 | failure_code = options[:failure_code] || :unauthorized | |
|
395 | ||
|
396 | context "should allow key based auth using key=X for #{http_method} #{url}" do | |
|
397 | context "with a valid api token" do | |
|
398 | setup do | |
|
399 | @user = User.generate! do |user| | |
|
400 | user.admin = true | |
|
401 | end | |
|
402 | @token = Token.create!(:user => @user, :action => 'api') | |
|
403 | # Simple url parse to add on ?key= or &key= | |
|
404 | request_url = if url.match(/\?/) | |
|
405 | url + "&key=#{@token.value}" | |
|
406 | else | |
|
407 | url + "?key=#{@token.value}" | |
|
408 | end | |
|
409 | send(http_method, request_url, parameters) | |
|
410 | end | |
|
411 | should_respond_with success_code | |
|
412 | should_respond_with_content_type_based_on_url(url) | |
|
413 | should_be_a_valid_response_string_based_on_url(url) | |
|
414 | should "login as the user" do | |
|
415 | assert_equal @user, User.current | |
|
416 | end | |
|
417 | end | |
|
418 | ||
|
419 | context "with an invalid api token" do | |
|
420 | setup do | |
|
421 | @user = User.generate! do |user| | |
|
422 | user.admin = true | |
|
423 | end | |
|
424 | @token = Token.create!(:user => @user, :action => 'feeds') | |
|
425 | # Simple url parse to add on ?key= or &key= | |
|
426 | request_url = if url.match(/\?/) | |
|
427 | url + "&key=#{@token.value}" | |
|
428 | else | |
|
429 | url + "?key=#{@token.value}" | |
|
430 | end | |
|
431 | send(http_method, request_url, parameters) | |
|
432 | end | |
|
433 | should_respond_with failure_code | |
|
434 | should_respond_with_content_type_based_on_url(url) | |
|
435 | should "not login as the user" do | |
|
436 | assert_equal User.anonymous, User.current | |
|
437 | end | |
|
438 | end | |
|
439 | end | |
|
440 | ||
|
441 | context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do | |
|
442 | setup do | |
|
443 | @user = User.generate! do |user| | |
|
444 | user.admin = true | |
|
445 | end | |
|
446 | @token = Token.create!(:user => @user, :action => 'api') | |
|
447 | send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s}) | |
|
448 | end | |
|
449 | should_respond_with success_code | |
|
450 | should_respond_with_content_type_based_on_url(url) | |
|
451 | should_be_a_valid_response_string_based_on_url(url) | |
|
452 | should "login as the user" do | |
|
453 | assert_equal @user, User.current | |
|
454 | end | |
|
455 | end | |
|
456 | end | |
|
457 | ||
|
458 | # Uses should_respond_with_content_type based on what's in the url: | |
|
459 | # | |
|
460 | # '/project/issues.xml' => should_respond_with_content_type :xml | |
|
461 | # '/project/issues.json' => should_respond_with_content_type :json | |
|
462 | # | |
|
463 | # @param [String] url Request | |
|
464 | def self.should_respond_with_content_type_based_on_url(url) | |
|
465 | case | |
|
466 | when url.match(/xml/i) | |
|
467 | should "respond with XML" do | |
|
468 | assert_equal 'application/xml', @response.content_type | |
|
469 | end | |
|
470 | when url.match(/json/i) | |
|
471 | should "respond with JSON" do | |
|
472 | assert_equal 'application/json', @response.content_type | |
|
473 | end | |
|
474 | else | |
|
475 | raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}" | |
|
476 | end | |
|
477 | end | |
|
478 | ||
|
479 | # Uses the url to assert which format the response should be in | |
|
480 | # | |
|
481 | # '/project/issues.xml' => should_be_a_valid_xml_string | |
|
482 | # '/project/issues.json' => should_be_a_valid_json_string | |
|
483 | # | |
|
484 | # @param [String] url Request | |
|
485 | def self.should_be_a_valid_response_string_based_on_url(url) | |
|
486 | case | |
|
487 | when url.match(/xml/i) | |
|
488 | should_be_a_valid_xml_string | |
|
489 | when url.match(/json/i) | |
|
490 | should_be_a_valid_json_string | |
|
491 | else | |
|
492 | raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}" | |
|
493 | end | |
|
494 | end | |
|
495 | ||
|
496 | # Checks that the response is a valid JSON string | |
|
497 | def self.should_be_a_valid_json_string | |
|
498 | should "be a valid JSON string (or empty)" do | |
|
499 | assert(response.body.blank? || ActiveSupport::JSON.decode(response.body)) | |
|
500 | end | |
|
501 | end | |
|
502 | ||
|
503 | # Checks that the response is a valid XML string | |
|
504 | def self.should_be_a_valid_xml_string | |
|
505 | should "be a valid XML string" do | |
|
506 | assert REXML::Document.new(response.body) | |
|
507 | end | |
|
508 | end | |
|
509 | ||
|
510 | def self.should_respond_with(status) | |
|
511 | should "respond with #{status}" do | |
|
512 | assert_response status | |
|
513 | end | |
|
514 | end | |
|
515 | 266 | end |
|
516 | 267 | |
|
517 | 268 | class Routing < Redmine::RoutingTest |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now