##// END OF EJS Templates
Test API authentification once....
Jean-Philippe Lang -
r13293:372b24627b70
parent child
Show More
@@ -28,6 +28,79 class Redmine::ApiTest::AuthenticationTest < Redmine::ApiTest::Base
28 Setting.rest_api_enabled = '0'
28 Setting.rest_api_enabled = '0'
29 end
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 def test_api_should_trigger_basic_http_auth_with_basic_authorization_header
104 def test_api_should_trigger_basic_http_auth_with_basic_authorization_header
32 ApplicationController.any_instance.expects(:authenticate_with_http_basic).once
105 ApplicationController.any_instance.expects(:authenticate_with_http_basic).once
33 get '/users/current.xml', {}, credentials('jsmith')
106 get '/users/current.xml', {}, credentials('jsmith')
@@ -48,44 +48,6 class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base
48 Setting.rest_api_enabled = '1'
48 Setting.rest_api_enabled = '1'
49 end
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 test "GET /issues.xml should contain metadata" do
51 test "GET /issues.xml should contain metadata" do
90 get '/issues.xml'
52 get '/issues.xml'
91 assert_select 'issues[type=array][total_count=?][limit="25"][offset="0"]',
53 assert_select 'issues[type=array][total_count=?][limit="25"][offset="0"]',
@@ -31,9 +31,6 class Redmine::ApiTest::NewsTest < Redmine::ApiTest::Base
31 Setting.rest_api_enabled = '1'
31 Setting.rest_api_enabled = '1'
32 end
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 test "GET /news.xml should return news" do
34 test "GET /news.xml should return news" do
38 get '/news.xml'
35 get '/news.xml'
39
36
@@ -27,23 +27,6 class Redmine::ApiTest::ProjectsTest < Redmine::ApiTest::Base
27 set_tmp_attachments_directory
27 set_tmp_attachments_directory
28 end
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 test "GET /projects.xml should return projects" do
30 test "GET /projects.xml should return projects" do
48 get '/projects.xml'
31 get '/projects.xml'
49 assert_response :success
32 assert_response :success
@@ -24,45 +24,6 class Redmine::ApiTest::UsersTest < Redmine::ApiTest::Base
24 Setting.rest_api_enabled = '1'
24 Setting.rest_api_enabled = '1'
25 end
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 test "GET /users/:id.xml should return the user" do
27 test "GET /users/:id.xml should return the user" do
67 get '/users/2.xml'
28 get '/users/2.xml'
68
29
@@ -263,255 +263,6 module Redmine
263
263
264 # Base class for API tests
264 # Base class for API tests
265 class Base < ActionDispatch::IntegrationTest
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 end
266 end
516
267
517 class Routing < Redmine::RoutingTest
268 class Routing < Redmine::RoutingTest
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now