392 lines
10 KiB
Ruby
392 lines
10 KiB
Ruby
|
require 'spec_helper_min'
|
||
|
require 'support/helpers'
|
||
|
|
||
|
describe 'Warden' do
|
||
|
def login
|
||
|
# Manual login because `login_as` skips normal warden hook processing
|
||
|
host! "#{@user.username}.localhost.lan"
|
||
|
post create_session_url(email: @user.email, password: @user.password)
|
||
|
end
|
||
|
|
||
|
def wrong_login
|
||
|
host! "#{@user.username}.localhost.lan"
|
||
|
post create_session_url(email: @user.email, password: 'bla')
|
||
|
end
|
||
|
|
||
|
def logout
|
||
|
# Manual login because `login_as` skips normal warden hook processing
|
||
|
host! "#{@user.username}.localhost.lan"
|
||
|
get logout_url
|
||
|
end
|
||
|
|
||
|
describe ':auth_api Strategy' do
|
||
|
include_context 'users helper'
|
||
|
include HelperMethods
|
||
|
|
||
|
def generate_api_key_url
|
||
|
api_keys_url(user_domain: @user_api_keys.username)
|
||
|
end
|
||
|
|
||
|
before :all do
|
||
|
@user_api_keys = FactoryGirl.create(:valid_user)
|
||
|
@master_api_key = Carto::ApiKey.where(user_id: @user_api_keys.id).master.first
|
||
|
end
|
||
|
|
||
|
after :all do
|
||
|
@user_api_keys.destroy
|
||
|
end
|
||
|
|
||
|
it 'authenticates with header' do
|
||
|
get_json generate_api_key_url, {}, http_json_headers.merge(
|
||
|
'Authorization' => 'Basic ' + Base64.strict_encode64("#{@user_api_keys.username}:#{@master_api_key.token}")
|
||
|
) do |response|
|
||
|
response.status.should eq 200
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'returns 401 if header is missing' do
|
||
|
get_json generate_api_key_url, {}, http_json_headers do |response|
|
||
|
response.status.should eq 401
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'returns 401 if header is malformed' do
|
||
|
get_json generate_api_key_url, {}, http_json_headers.merge(
|
||
|
'Authorization' => 'Basicss ' + Base64.encode64("#{@user_api_keys.username}:#{@master_api_key.token}")
|
||
|
) do |response|
|
||
|
response.status.should eq 401
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'returns 401 if base64 is malforemd' do
|
||
|
get_json generate_api_key_url, {}, http_json_headers.merge(
|
||
|
'Authorization' => 'Basic ' + "asda2" + Base64.encode64("#{@user_api_keys.username}:#{@master_api_key.token}")
|
||
|
) do |response|
|
||
|
response.status.should eq 401
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe 'password expiration' do
|
||
|
include HelperMethods
|
||
|
|
||
|
before(:all) do
|
||
|
@user = FactoryGirl.create(:valid_user)
|
||
|
@user.password = @user.password_confirmation = '000qwaszx'
|
||
|
@user.save
|
||
|
end
|
||
|
|
||
|
after(:all) do
|
||
|
@user.destroy
|
||
|
end
|
||
|
|
||
|
let (:session_expired_message) { 'Your session has expired' }
|
||
|
|
||
|
it 'allows access for non-expired session' do
|
||
|
# we use this to avoid generating the static assets in CI
|
||
|
Admin::VisualizationsController.any_instance.stubs(:render).returns('')
|
||
|
|
||
|
Cartodb.with_config(passwords: { 'expiration_in_d' => nil }) do
|
||
|
login
|
||
|
|
||
|
host! "#{@user.username}.localhost.lan"
|
||
|
get dashboard_url
|
||
|
|
||
|
expect(response.status).to eq 200
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'UI redirects to login page if password is expired' do
|
||
|
Cartodb::Central.any_instance.stubs(:send_request)
|
||
|
|
||
|
login
|
||
|
|
||
|
Cartodb.with_config(passwords: { 'expiration_in_d' => 1 }) do
|
||
|
Delorean.jump(2.days)
|
||
|
|
||
|
host! "#{@user.username}.localhost.lan"
|
||
|
get dashboard_url
|
||
|
|
||
|
expect(response.status).to eq 302
|
||
|
follow_redirect!
|
||
|
|
||
|
expect(request.fullpath).to end_with "/login?error=session_expired"
|
||
|
Delorean.back_to_the_present
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'redirects to the original url after changing the expired password' do
|
||
|
# we use this to avoid generating the static assets in CI
|
||
|
Admin::VisualizationsController.any_instance.stubs(:render).returns('')
|
||
|
Cartodb::Central.any_instance.stubs(:send_request)
|
||
|
|
||
|
login
|
||
|
|
||
|
Cartodb.with_config(passwords: { 'expiration_in_d' => 1 }) do
|
||
|
Delorean.jump(2.days)
|
||
|
|
||
|
get notifications_url
|
||
|
follow_redirect!
|
||
|
|
||
|
login
|
||
|
follow_redirect!
|
||
|
|
||
|
change_password_payload = {
|
||
|
username: @user.username, old_password: @user.password, password: 'pass123', password_confirmation: 'pass123'
|
||
|
}
|
||
|
put password_change_url(@user.username), change_password_payload
|
||
|
follow_redirect!
|
||
|
|
||
|
request.path.should eq notifications_path
|
||
|
|
||
|
Delorean.back_to_the_present
|
||
|
end
|
||
|
|
||
|
@user.password = @user.password_confirmation = '000qwaszx'
|
||
|
@user.save
|
||
|
end
|
||
|
|
||
|
it 'API returns 403 with an error if password is expired' do
|
||
|
Cartodb::Central.any_instance.stubs(:send_request)
|
||
|
|
||
|
login
|
||
|
|
||
|
Cartodb.with_config(passwords: { 'expiration_in_d' => 1 }) do
|
||
|
Delorean.jump(2.days)
|
||
|
|
||
|
host! "#{@user.username}.localhost.lan"
|
||
|
get_json api_v3_users_me_url
|
||
|
|
||
|
expect(response.status).to eq 403
|
||
|
expect(JSON.parse(response.body)).to eq('error' => 'session_expired')
|
||
|
Delorean.back_to_the_present
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'does not allow access password_change if password is not expired' do
|
||
|
login
|
||
|
|
||
|
Cartodb.with_config(passwords: { 'expiration_in_d' => nil }) do
|
||
|
host! "#{@user.username}.localhost.lan"
|
||
|
get edit_password_change_path(@user.username)
|
||
|
|
||
|
expect(response.status).to eq 403
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'does not validate password expiration for API-key requests' do
|
||
|
Cartodb.with_config(passwords: { 'expiration_in_d' => 1 }) do
|
||
|
Delorean.jump(2.days)
|
||
|
|
||
|
get_json api_v3_users_me_url, user_domain: @user.username, api_key: @user.api_key do |response|
|
||
|
expect(response.status).to eq 200
|
||
|
expect(response.body[:user_data]).to be
|
||
|
end
|
||
|
|
||
|
Delorean.back_to_the_present
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
shared_examples_for 'login locked' do
|
||
|
include HelperMethods
|
||
|
|
||
|
before(:each) do
|
||
|
SessionsController.any_instance.stubs(:central_enabled?).returns(false)
|
||
|
# we use this to avoid generating the static assets in CI
|
||
|
Admin::VisualizationsController.any_instance.stubs(:render).returns('')
|
||
|
end
|
||
|
|
||
|
def expect_password_locked
|
||
|
expect(response.status).to eq 302
|
||
|
follow_redirect!
|
||
|
|
||
|
expect(request.fullpath).to include "/login?error=password_locked"
|
||
|
expect(response.body).to include "Too many failed login attempts"
|
||
|
end
|
||
|
|
||
|
def expect_login
|
||
|
host! "#{@user.username}.localhost.lan"
|
||
|
get dashboard_url
|
||
|
|
||
|
expect(response.status).to eq 200
|
||
|
end
|
||
|
|
||
|
it 'redirects to login page with an error if password is locked' do
|
||
|
Cartodb.with_config(
|
||
|
passwords: {
|
||
|
'rate_limit' => {
|
||
|
'max_burst' => 0,
|
||
|
'count' => 1,
|
||
|
'period' => 60
|
||
|
}
|
||
|
}
|
||
|
) do
|
||
|
@user.reset_password_rate_limit
|
||
|
wrong_login
|
||
|
wrong_login
|
||
|
|
||
|
expect_password_locked
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'allows to login after the locked password period' do
|
||
|
Cartodb.with_config(
|
||
|
passwords: {
|
||
|
'rate_limit' => {
|
||
|
'max_burst' => 0,
|
||
|
'count' => 1,
|
||
|
'period' => 1
|
||
|
}
|
||
|
}
|
||
|
) do
|
||
|
@user.reset_password_rate_limit
|
||
|
wrong_login
|
||
|
wrong_login
|
||
|
expect_password_locked
|
||
|
|
||
|
sleep(3)
|
||
|
|
||
|
login
|
||
|
expect_login
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'does not allow to login during the locked password period' do
|
||
|
Cartodb.with_config(
|
||
|
passwords: {
|
||
|
'rate_limit' => {
|
||
|
'max_burst' => 0,
|
||
|
'count' => 1,
|
||
|
'period' => 10
|
||
|
}
|
||
|
}
|
||
|
) do
|
||
|
@user.reset_password_rate_limit
|
||
|
wrong_login
|
||
|
wrong_login
|
||
|
|
||
|
login
|
||
|
expect_password_locked
|
||
|
end
|
||
|
end
|
||
|
|
||
|
it 'allows to login if password is changed' do
|
||
|
Cartodb.with_config(
|
||
|
passwords: {
|
||
|
'rate_limit' => {
|
||
|
'max_burst' => 0,
|
||
|
'count' => 2,
|
||
|
'period' => 10
|
||
|
}
|
||
|
}
|
||
|
) do
|
||
|
@user.reset_password_rate_limit
|
||
|
wrong_login
|
||
|
wrong_login
|
||
|
expect_password_locked
|
||
|
|
||
|
old_password = @user.password
|
||
|
new_password = '00012345678'
|
||
|
@user.password = @user.password_confirmation = new_password
|
||
|
|
||
|
@user.save
|
||
|
|
||
|
login
|
||
|
expect_login
|
||
|
|
||
|
@user.password = @user.password_confirmation = old_password
|
||
|
@user.save
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe 'with Sequel user' do
|
||
|
it_behaves_like 'login locked' do
|
||
|
before(:all) do
|
||
|
@user = FactoryGirl.create(:user, password: '000qwaszx', password_confirmation: '000qwaszx')
|
||
|
end
|
||
|
|
||
|
after(:all) do
|
||
|
@user.destroy
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe 'with AR user' do
|
||
|
it_behaves_like 'login locked' do
|
||
|
before(:all) do
|
||
|
@user = FactoryGirl.create(:carto_user, password: '000qwaszx', password_confirmation: '000qwaszx')
|
||
|
end
|
||
|
|
||
|
after(:all) do
|
||
|
@user.destroy
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
describe 'session' do
|
||
|
before(:all) do
|
||
|
@user = FactoryGirl.create(:valid_user)
|
||
|
end
|
||
|
|
||
|
before(:each) do
|
||
|
Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(true)
|
||
|
Cartodb::Central.any_instance.stubs(:send_request)
|
||
|
end
|
||
|
|
||
|
after(:all) do
|
||
|
@user.destroy
|
||
|
end
|
||
|
|
||
|
after(:each) do
|
||
|
Cartodb::Central.unstub(:sync_data_with_cartodb_central?)
|
||
|
Cartodb::Central.any_instance.unstub(:send_request)
|
||
|
end
|
||
|
|
||
|
it 'should be valid for current security token ' do
|
||
|
# we use this to avoid generating the static assets in CI
|
||
|
Admin::VisualizationsController.any_instance.stubs(:render).returns('')
|
||
|
|
||
|
login
|
||
|
cookies["_cartodb_session"] = response.cookies["_cartodb_session"]
|
||
|
|
||
|
get dashboard_url
|
||
|
|
||
|
response.status.should == 200
|
||
|
end
|
||
|
|
||
|
# Skip until invalid session forces a new login
|
||
|
xit 'should not be valid for different security token' do
|
||
|
login
|
||
|
|
||
|
@user.session_salt = "1234567f"
|
||
|
@user.save
|
||
|
|
||
|
get account_user_url
|
||
|
|
||
|
response.status.should == 302
|
||
|
request.fullpath.should eql '/account'
|
||
|
|
||
|
follow_redirect!
|
||
|
|
||
|
response.status.should == 200
|
||
|
request.fullpath.should eql '/login'
|
||
|
end
|
||
|
|
||
|
it 'updates the session_salt at logout' do
|
||
|
initial_session_salt = @user.session_salt
|
||
|
|
||
|
logout
|
||
|
|
||
|
initial_session_salt.should_not == @user.reload.session_salt
|
||
|
end
|
||
|
|
||
|
it 'updates the user in Central at logout' do
|
||
|
Cartodb::Central.any_instance.expects(:send_request).once
|
||
|
|
||
|
logout
|
||
|
end
|
||
|
end
|
||
|
end
|