require_relative '../spec_helper' require_relative '../helpers/subdomainless_helper' require 'fake_net_ldap' require_relative '../lib/fake_net_ldap_bind_as' describe SessionsController do after(:each) do Cartodb::Central.unstub(:sync_data_with_cartodb_central?) end def create_ldap_user(admin_user_username, admin_user_password) admin_user_email = "#{@organization.name}-admin@test.com" admin_user_cn = "cn=#{admin_user_username},#{@domain_bases.first}" ldap_entry_data = { :dn => admin_user_cn, @user_id_field => [admin_user_username], @user_email_field => [admin_user_email] } FakeNetLdap.register_user(username: admin_user_cn, password: admin_user_password) FakeNetLdap.register_query(Net::LDAP::Filter.eq('cn', admin_user_username), [ldap_entry_data]) create_admin_user(admin_user_username, admin_user_email, admin_user_password) @admin_user.save.reload ::Organization.any_instance.stubs(:owner).returns(@admin_user) # INFO: Again, hack to act as if user had organization ::User.stubs(:where).with(username: admin_user_username, organization_id: @organization.id).returns([@admin_user]) end def create_admin_user(admin_user_username, admin_user_email, admin_user_password) @admin_user = create_user( username: admin_user_username, email: admin_user_email, password: admin_user_password, private_tables_enabled: true, quota_in_bytes: 12345, organization: nil ) end shared_examples_for 'LDAP' do it "doesn't allows to login until admin does first" do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) normal_user_username = "ldap-user" normal_user_password = "2{Patrañas}" normal_user_email = "ldap-user@test.com" normal_user_cn = "cn=#{normal_user_username},#{@domain_bases.first}" ldap_entry_data = { :dn => normal_user_cn, @user_id_field => [normal_user_username], @user_email_field => [normal_user_email] } FakeNetLdap.register_user(username: normal_user_cn, password: normal_user_password) FakeNetLdap.register_query(Net::LDAP::Filter.eq('cn', normal_user_username), [ldap_entry_data]) errors = { errors: { organization: ["Organization owner is not set. Administrator must login first."] } } ::CartoDB.expects(:notify_debug).with('User not valid at signup', errors).returns(nil) post create_session_url(user_domain: user_domain, email: normal_user_username, password: normal_user_password) response.status.should == 200 (response.body =~ /Signup issues/).to_i.should_not eq 0 end it "Allows to login and triggers creation if using the org admin account" do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) # @See lib/user_account_creator.rb -> promote_to_organization_owner? admin_user_username = "#{@organization.name}-admin" admin_user_password = '2{Patrañas}' admin_user_email = "#{@organization.name}-admin@test.com" admin_user_cn = "cn=#{admin_user_username},#{@domain_bases.first}" ldap_entry_data = { :dn => admin_user_cn, @user_id_field => [admin_user_username], @user_email_field => [admin_user_email] } FakeNetLdap.register_user(username: admin_user_cn, password: admin_user_password) FakeNetLdap.register_query(Net::LDAP::Filter.eq('cn', admin_user_username), [ldap_entry_data]) ::Resque.expects(:enqueue).with(::Resque::UserJobs::Signup::NewUser, instance_of(String), anything, instance_of(TrueClass)).returns(true) post create_session_url(user_domain: user_domain, email: admin_user_username, password: admin_user_password) response.status.should == 200 (response.body =~ /Your account is being created/).to_i.should_not eq 0 end it "Allows to login and triggers creation of normal users if admin already present" do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) admin_user_username = "#{@organization.name}-admin" admin_user_password = '2{Patrañas}' admin_user_email = "#{@organization.name}-admin@test.com" @admin_user = create_user( username: admin_user_username, email: admin_user_email, password: admin_user_password, private_tables_enabled: true, quota_in_bytes: 12345, organization: nil ) @admin_user.save.reload # INFO: Hack to avoid having to destroy and recreate later the organization ::Organization.any_instance.stubs(:owner).returns(@admin_user) normal_user_username = "ldap-user" normal_user_password = "foobar" normal_user_email = "ldap-user@test.com" normal_user_cn = "cn=#{normal_user_username},#{@domain_bases.first}" ldap_entry_data = { :dn => normal_user_cn, @user_id_field => [normal_user_username], @user_email_field => [normal_user_email] } FakeNetLdap.register_user(username: normal_user_cn, password: normal_user_password) FakeNetLdap.register_query(Net::LDAP::Filter.eq('cn', normal_user_username), [ldap_entry_data]) ::Resque.expects(:enqueue).with(::Resque::UserJobs::Signup::NewUser, instance_of(String), anything, instance_of(FalseClass)).returns(true) post create_session_url(user_domain: user_domain, email: normal_user_username, password: normal_user_password) response.status.should == 200 (response.body =~ /Your account is being created/).to_i.should_not eq 0 @admin_user.destroy end it "Just logs in if finds a cartodb username that matches with LDAP credentials " do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) admin_user_username = "#{@organization.name}-admin" admin_user_password = '2{Patrañas}' create_ldap_user(admin_user_username, admin_user_password) post create_session_url(user_domain: user_domain, email: admin_user_username, password: admin_user_password) response.status.should == 302 (response.location =~ /^http\:\/\/#{admin_user_username}(.*)\/dashboard\/$/).to_i.should eq 0 ::User.unstub(:where) @admin_user.destroy end it "Falls back to credentials if user is not present at LDAP" do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) admin_user_username = "#{@organization.name}-admin" admin_user_password = '2{Patrañas}' admin_user_email = "#{@organization.name}-admin@test.com" admin_user_cn = "cn=#{admin_user_username},#{@domain_bases.first}" ldap_entry_data = { :dn => admin_user_cn, @user_id_field => [admin_user_username], @user_email_field => [admin_user_email] } FakeNetLdap.register_user(username: admin_user_cn, password: 'fake') FakeNetLdap.register_query(Net::LDAP::Filter.eq('cn', admin_user_username), [ldap_entry_data]) @admin_user = create_user( username: admin_user_username, email: admin_user_email, password: admin_user_password, private_tables_enabled: true, quota_in_bytes: 12345, organization: nil ) @admin_user.save.reload ::Organization.any_instance.stubs(:owner).returns(@admin_user) # INFO: Again, hack to act as if user had organization ::User.stubs(:where).with(username: admin_user_username, organization_id: @organization.id).returns([@admin_user]) post create_session_url(user_domain: user_domain, email: admin_user_username, password: admin_user_password) response.status.should == 302 (response.location =~ /^http\:\/\/#{admin_user_username}(.*)\/dashboard\/$/).to_i.should eq 0 ::User.unstub(:where) @admin_user.destroy end shared_examples_for 'MFA' do it "Redirects to multifactor_authentication if finds a cartodb username that matches with LDAP credentials" do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) admin_user_username = "#{@organization.name}-admin" admin_user_password = '2{Patrañas}' create_ldap_user(admin_user_username, admin_user_password) post create_session_url(user_domain: user_domain, email: admin_user_username, password: admin_user_password) response.status.should == 302 (response.location =~ /^http\:\/\/#{admin_user_username}(.*)\/dashboard\/$/).to_i.should eq 0 get response.redirect_url response.status.should == 302 (response.location =~ /^http\:\/\/#{admin_user_username}(.*)\/multifactor_authentication\/$/).to_i.should eq 0 ::User.unstub(:where) @admin_user.destroy end end end describe 'LDAP authentication' do DEFAULT_QUOTA_IN_BYTES = 1000 before(:all) do bypass_named_maps @organization = ::Organization.new @organization.seats = 5 @organization.quota_in_bytes = 100.megabytes @organization.name = "ldap-org" @organization.default_quota_in_bytes = DEFAULT_QUOTA_IN_BYTES @organization.save @domain_bases = ["dc=cartodb"] @ldap_admin_username = 'user' @ldap_admin_cn = "cn=#{@ldap_admin_username},#{@domain_bases[0]}" @ldap_admin_password = '666' @user_id_field = 'cn' @user_email_field = 'mail' @ldap_config = Carto::Ldap::Configuration.create(organization_id: @organization.id, host: "0.0.0.0", port: 389, domain_bases_list: @domain_bases, connection_user: @ldap_admin_cn, connection_password: @ldap_admin_password, email_field: @user_email_field, user_object_class: '.', group_object_class: '.', user_id_field: @user_id_field, username_field: @user_id_field) end before(:each) do bypass_named_maps FakeNetLdap.register_user(username: @ldap_admin_cn, password: @ldap_admin_password) end after(:each) do FakeNetLdap.clear_user_registrations FakeNetLdap.clear_query_registrations end after(:all) do bypass_named_maps @ldap_config.delete @organization.destroy_cascade end describe 'domainful' do it_behaves_like 'LDAP' let(:user_domain) { nil } before(:each) do stub_domainful(@organization.name) end describe 'MFA' do def create_admin_user(admin_user_username, admin_user_email, admin_user_password) @admin_user = create_user( username: admin_user_username, email: admin_user_email, password: admin_user_password, private_tables_enabled: true, quota_in_bytes: 12345, organization: nil ) @admin_user.user_multifactor_auths << FactoryGirl.create(:totp, :active, user_id: @admin_user.id) @admin_user.save end it_behaves_like 'LDAP' it_behaves_like 'MFA' end end describe 'subdomainless' do it_behaves_like 'LDAP' let(:user_domain) { @organization.name } before(:each) do stub_subdomainless end describe 'MFA' do def create_admin_user(admin_user_username, admin_user_email, admin_user_password) @admin_user = create_user( username: admin_user_username, email: admin_user_email, password: admin_user_password, private_tables_enabled: true, quota_in_bytes: 12345, organization: nil ) @admin_user.user_multifactor_auths << FactoryGirl.create(:totp, :active, user_id: @admin_user.id) @admin_user.save end it_behaves_like 'LDAP' it_behaves_like 'MFA' end end end shared_examples_for 'SAML' do def stub_saml_service(user) Carto::SamlService.any_instance.stubs(:enabled?).returns(true) Carto::SamlService.any_instance.stubs(:get_user_email).returns(user.email) end it 'redirects to SAML authentication request if enabled' do authentication_request = "http://fakesaml.com/authenticate" Carto::SamlService.any_instance.stubs(:enabled?).returns(true) Carto::SamlService.any_instance.stubs(:authentication_request).returns(authentication_request) get login_url(user_domain: user_domain) response.location.should eq authentication_request response.status.should eq 302 end it 'authenticates with SAML if SAMLResponse is present and SAML is enabled' do stub_saml_service(@user) SessionsController.any_instance.expects(:authenticate!).with(:saml, scope: @user.username).returns(@user).once post create_session_url(user_domain: user_domain, SAMLResponse: 'xx') end it "Allows to login and triggers creation of normal users if user is not present" do new_user = FactoryGirl.build(:carto_user, username: 'new-saml-user', email: 'new-saml-user-email@carto.com') stub_saml_service(new_user) Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) ::Resque.expects(:enqueue).with(::Resque::UserJobs::Signup::NewUser, instance_of(String), anything, instance_of(FalseClass)).returns(true) post create_session_url(user_domain: user_domain, SAMLResponse: 'xx') response.status.should == 200 (response.body =~ /Your account is being created/).to_i.should_not eq 0 ::User.where(username: new_user.username).first.try(:destroy) end it "Allows to login and triggers creation of normal users if user is not present" do new_user = FactoryGirl.build(:carto_user, username: 'new-saml-user', email: 'new-saml-user-email@carto.com') stub_saml_service(new_user) Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) ::Resque.expects(:enqueue).with(::Resque::UserJobs::Signup::NewUser, instance_of(String), anything, instance_of(FalseClass)).returns(true) post create_session_url(user_domain: user_domain, SAMLResponse: 'xx') response.status.should == 200 (response.body =~ /Your account is being created/).to_i.should_not eq 0 ::User.where(username: new_user.username).first.try(:destroy) end it "Fails to authenticate if SAML request fails" do Carto::SamlService.any_instance.stubs(:enabled?).returns(true) Carto::SamlService.any_instance.stubs(:get_user_email).returns(nil) post create_session_url(user_domain: user_domain, SAMLResponse: 'xx') response.status.should == 403 end describe 'SAML logout' do it 'calls SamlService#sp_logout_request from user-initiated logout' do stub_saml_service(@user) SessionsController.any_instance.expects(:authenticate!).with(:saml, scope: @user.username).returns(@user).once post create_session_url(user_domain: user_domain, SAMLResponse: 'xx') # needs returning an url to do a redirection Carto::SamlService.any_instance.stubs(:sp_logout_request).returns('http://carto.com').once get logout_url(user_domain: user_domain) end it 'does not call SamlService#sp_logout_request if logout URL is not configured' do stub_saml_service(@user) SessionsController.any_instance.expects(:authenticate!).with(:saml, scope: @user.username).returns(@user).once post create_session_url(user_domain: user_domain, SAMLResponse: 'xx') # needs returning an url to do a redirection Carto::SamlService.any_instance.stubs(:logout_url_configured?).returns(false) Carto::SamlService.any_instance.stubs(:sp_logout_request).returns('http://carto.com').never get logout_url(user_domain: user_domain) end it 'calls SamlService#idp_logout_request if SAMLRequest is present' do # needs returning an url to do a redirection Carto::SamlService.any_instance.stubs(:idp_logout_request).returns('http://carto.com').once get logout_url(user_domain: user_domain, SAMLRequest: 'xx') end it 'calls SamlService#process_logout_response if SAMLResponse is present' do # needs returning an url to do a redirection Carto::SamlService.any_instance.stubs(:process_logout_response).returns('http://carto.com').once get logout_url(user_domain: user_domain, SAMLResponse: 'xx') end end shared_examples_for 'SAML no MFA' do it "authenticates users with casing differences in email" do # we use this to avoid generating the static assets in CI Admin::VisualizationsController.any_instance.stubs(:render).returns('') Carto::SamlService.any_instance.stubs(:enabled?).returns(true) Carto::SamlService.any_instance.stubs(:get_user_email).returns(@user.email.upcase) post create_session_url(user_domain: user_domain, SAMLResponse: 'xx') response.status.should eq 302 # Double check authentication is correct get response.redirect_url response.status.should eq 200 end end end describe 'SAML authentication' do def create @organization = FactoryGirl.create(:saml_organization, quota_in_bytes: 1.gigabytes) @admin_user = create_admin_user(@organization) @user = FactoryGirl.create(:carto_user) @user.organization_id = @organization.id @user.save end def cleanup @user.destroy @organization.destroy @admin_user.destroy end def create_admin_user(organization) admin_user_username = "#{organization.name}-admin" admin_user_email = "#{organization.name}-admin@test.com" admin_user = create_user( username: admin_user_username, email: admin_user_email, password: '2{Patrañas}', private_tables_enabled: true, quota_in_bytes: 12345, organization: nil ) admin_user.save.reload @organization.owner = admin_user @organization.save admin_user end describe 'domainful' do it_behaves_like 'SAML' it_behaves_like 'SAML no MFA' let(:user_domain) { nil } before(:each) do stub_domainful(@organization.name) end before(:all) do create end after(:all) do cleanup end end describe 'subdomainless' do it_behaves_like 'SAML' it_behaves_like 'SAML no MFA' let(:user_domain) { @organization.name } before(:each) do stub_subdomainless end before(:all) do create end after(:all) do cleanup end end describe 'user with MFA' do it_behaves_like 'SAML' it "redirects to multifactor_authentication" do # we use this to avoid generating the static assets in CI Admin::VisualizationsController.any_instance.stubs(:render).returns('') Carto::SamlService.any_instance.stubs(:enabled?).returns(true) Carto::SamlService.any_instance.stubs(:get_user_email).returns(@user.email) post create_session_url(user_domain: user_domain, SAMLResponse: 'xx') response.status.should eq 302 get response.redirect_url response.status.should eq 302 response.redirect_url.should include '/multifactor_authentication' end let(:user_domain) { nil } before(:each) do stub_domainful(@organization.name) end before(:all) do create Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) @user.user_multifactor_auths << FactoryGirl.create(:totp, :active, user_id: @user.id) @user.save @admin_user.user_multifactor_auths << FactoryGirl.create(:totp, :active, user_id: @admin_user.id) @admin_user.save end after(:all) do cleanup end end end describe '#login' do before(:all) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(true) @organization = FactoryGirl.create(:organization) @user = FactoryGirl.create(:carto_user) end after(:all) do @user.destroy @organization.destroy end describe 'with Central' do before(:each) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(true) end it 'redirects to Central for user logins' do get login_url(user_domain: @user.username) response.status.should == 302 end it 'redirects to Central for orgs without any auth method enabled' do Carto::Organization.any_instance.stubs(:auth_enabled?).returns(false) get login_url(user_domain: @organization.name) response.status.should == 302 end it 'uses the box login for orgs with any auth enabled' do Carto::Organization.any_instance.stubs(:auth_enabled?).returns(true) get login_url(user_domain: @organization.name) response.status.should == 200 end it 'disallows login from an organization login page to a non-member' do Carto::Organization.any_instance.stubs(:auth_enabled?).returns(true) post create_session_url(user_domain: @organization.name, email: @user.username, password: @user.password) response.status.should == 200 response.body.should include 'Not a member' end end describe 'without Central' do before(:each) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) end it 'does not redirect' do get login_url(user_domain: @user.username) response.status.should == 200 end it 'allows login from an organization login page to a non-member' do Carto::Organization.any_instance.stubs(:auth_enabled?).returns(true) post create_session_url(user_domain: @organization.name, email: @user.username, password: @user.password) response.status.should == 302 end it 'redirects to dashboard if `return_to` url is not present' do post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) response.status.should == 302 response.headers['Location'].should include '/dashboard' end it 'redirects to the `return_to` url if present' do get api_key_credentials_url(user_domain: @user.username) cookies["_cartodb_session"] = response.cookies["_cartodb_session"] post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) response.status.should == 302 response.headers['Location'].should include '/your_apps' end it 'redirects to current viewer dashboard if the `return_to` dashboard url belongs to other user' do post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) cookies["_cartodb_session"] = response.cookies["_cartodb_session"] get login_url(user_domain: 'wadus_user') response.headers['Location'].should include @user.username response.headers['Location'].should include "/dashboard" end it 'redirects to the `return_to` only once url if present' do get api_key_credentials_url(user_domain: @user.username) cookies["_cartodb_session"] = response.cookies["_cartodb_session"] post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) response.status.should == 302 response.headers['Location'].should include '/your_apps' Marshal.dump(Base64.decode64(response.cookies["_cartodb_session"]))['return_to'].should be_nil end it 'creates _cartodb_base_url cookie' do post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) response.cookies['_cartodb_base_url'].should eq CartoDB.base_url(@user.username) end end describe 'events' do # include HttpAuthenticationHelper require 'rack/test' include Rack::Test::Methods include Warden::Test::Helpers it 'triggers CartoGearsApi::Events::UserLoginEvent' do CartoGearsApi::Events::EventManager.any_instance.expects(:notify).once.with do |event| event.class.should eq CartoGearsApi::Events::UserLoginEvent end post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) end it 'sets dashboard_viewed_at just with login' do @user.update_column(:dashboard_viewed_at, nil) @user.reload @user.dashboard_viewed_at.should be_nil post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) @user.reload @user.dashboard_viewed_at.should_not be_nil end include Warden::Test::Helpers it 'triggers CartoGearsApi::Events::UserLoginEvent signaling not first login' do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) login(::User.where(id: @user.id).first) logout CartoGearsApi::Events::EventManager.any_instance.expects(:notify).once.with do |event| event.first_login?.should be_false end post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) end it 'triggers CartoGearsApi::Events::UserLoginEvent signaling first login' do @new_user = FactoryGirl.create(:carto_user) CartoGearsApi::Events::EventManager.any_instance.expects(:notify).once.with do |event| event.first_login?.should be_true end post create_session_url(user_domain: @new_user.username, email: @new_user.username, password: @new_user.password) @new_user.destroy end it 'returns a CartoGearsApi::Users::User matching the logged user' do CartoGearsApi::Events::EventManager.any_instance.expects(:notify).once.with do |event| event_user = event.user event_user.class.should eq CartoGearsApi::Users::User event_user.username.should eq @user.username end post create_session_url(user_domain: @user.username, email: @user.username, password: @user.password) end end end describe '#multifactor_authentication' do include Warden::Test::Helpers def login(user = @user) logout host! "#{user.username}.localhost.lan" login_as(user, scope: user.username) end def create_session post create_session_url(email: @user.username, password: @user.password) end def code ROTP::TOTP.new(@user.active_multifactor_authentication.shared_secret).now end def expect_login_error response.status.should eq 200 request.path.should_not include '/dashboard' response.body.should include 'Sessions-fieldError' end def expect_login response.status.should eq 302 response.headers['Location'].should include "/dashboard" end def expect_invalid_code response.status.should eq 200 request.path.should include '/multifactor_authentication' response.body.should include 'Verification code is not valid' end shared_examples_for 'all users workflow' do before(:each) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) @user.user_multifactor_auths.each(&:destroy) @user.user_multifactor_auths << FactoryGirl.create(:totp, :active, user_id: @user.id) @user.reload @user.reset_password_rate_limit end after(:each) do SessionsController::MAX_MULTIFACTOR_AUTHENTICATION_INACTIVITY = 120.seconds end it 'verifies a valid code' do login get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, code: code) expect_login end it 'redirects to login and then to code verification when there is no session' do get dashboard_url follow_redirect! login post create_session_url(email: @user.username, password: @user.password) ApplicationController.any_instance.stubs(:current_viewer).returns(@user) ApplicationController.any_instance.stubs(:multifactor_authentication_required?).returns(true) follow_redirect! request.path.should eq multifactor_authentication_verify_code_path end it 'does not verify an invalid code' do login get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, code: 'invalid_code') expect_invalid_code end it 'does not verify an already used code' do login get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, code: code) expect_login logout login get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, code: code) expect_invalid_code end it 'logout if user inactive' do login SessionsController::MAX_MULTIFACTOR_AUTHENTICATION_INACTIVITY = -1 get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, code: code) response.status.should eq 302 response.headers['Location'].should include 'login?error=multifactor_authentication_inactivity' follow_redirect! response.status.should eq 200 response.body.should include("You've been logged out due to a long time of inactivity") end it 'rate limits verification code' do login Cartodb.with_config( passwords: { 'rate_limit' => { 'max_burst' => 0, 'count' => 1, 'period' => 10 } } ) do @user.reset_password_rate_limit get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, code: 'invalid_code') post multifactor_authentication_verify_code_url(user_id: @user.id, code: 'invalid_code') response.status.should eq 302 response.headers['Location'].should include '/login?error=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' => 3 } } ) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) @user.reset_password_rate_limit login get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, code: 'invalid_code') expect_invalid_code post multifactor_authentication_verify_code_url(user_id: @user.id, code: 'invalid_code') response.status.should eq 302 response.headers['Location'].should include '/login?error=password_locked' @user.reload sleep(4) login get multifactor_authentication_session_url cookies["_cartodb_session"] = response.cookies["_cartodb_session"] post multifactor_authentication_verify_code_url(user_id: @user.id, code: code) expect_login end end context 'skipping MFA configuration' do before(:each) do mfa = @user.active_multifactor_authentication mfa.enabled = false mfa.save! end after(:each) do FactoryGirl.create(:totp, :needs_setup, user_id: @user.id) @user.reload end it 'skips configuration only when mfa needs setup' do login get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, skip: true) expect_login end it 'removes user multifactor auths when mfa configuration is skipped' do login get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, skip: true) @user.reload.user_multifactor_auths.should be_empty end it 'does not allow to skip verification if is active' do mfa = @user.active_multifactor_authentication mfa.enabled = true mfa.save! login get multifactor_authentication_session_url post multifactor_authentication_verify_code_url(user_id: @user.id, skip: true) expect_invalid_code end end end shared_examples_for 'organizational user' do shared_examples_for 'organization custom view' do it 'shows organization custom view' do get multifactor_authentication_session_url expect(response.body).to include(@organization.name) end end context 'subdomainless' do before(:each) do stub_subdomainless login end include_examples 'organization custom view' end context 'domainful' do before(:each) do stub_domainful(@organization.name) login end include_examples 'organization custom view' end end describe 'as individual user' do before(:all) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) @user = FactoryGirl.create(:carto_user_mfa) end after(:all) do @user.destroy end it_behaves_like 'all users workflow' end describe 'as org owner' do before(:all) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) @organization = FactoryGirl.create(:organization_with_users, :mfa_enabled) @user = @organization.owner @user.password = @user.password_confirmation = @user.crypted_password = '00012345678' @user.save end after(:all) do @organization.destroy end def create_session post create_session_url(user_domain: @user.username, email: @user.username, password: '00012345678') end it_behaves_like 'all users workflow' it_behaves_like 'organizational user' end describe 'as org user' do before(:all) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) @organization = FactoryGirl.create(:organization_with_users, :mfa_enabled) @user = @organization.users.last @user.password = @user.password_confirmation = @user.crypted_password = '00012345678' @user.save end after(:all) do @organization.destroy end def create_session post create_session_url(user_domain: @user.username, email: @user.username, password: '00012345678') end it_behaves_like 'all users workflow' it_behaves_like 'organizational user' end describe 'as org without user pass enabled' do before(:all) do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) Carto::Organization.any_instance.stubs(:auth_enabled?).returns(true) @organization = FactoryGirl.create(:organization_with_users, :mfa_enabled, auth_username_password_enabled: false) @user = @organization.users.last @user.password = @user.password_confirmation = @user.crypted_password = '00012345678' @user.save end after(:all) do @organization.destroy end def login(user = @user) logout host! "#{@organization.name}.localhost.lan" login_as(user, scope: user.username) end def create_session post create_session_url(user_domain: @organization.name, email: @user.username, password: @user.password) end it_behaves_like 'all users workflow' end end describe '#logout' do before(:all) do @user = FactoryGirl.create(:carto_user) end after(:all) do @user.destroy end shared_examples_for 'logout endpoint' do it 'redirects to user dashboard' do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) post create_session_url(email: @user.username, password: @user.password) get CartoDB.base_url(@user.username) + logout_path response.status.should eq 302 response.location.should include @user.username end end describe 'domainful' do it_behaves_like 'logout endpoint' before(:each) do stub_domainful(@user.username) Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) end end describe 'subdomainless' do it_behaves_like 'logout endpoint' before(:each) do stub_subdomainless Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) end end end describe '#destroy' do it 'deletes the _cartodb_base_url cookie' do Cartodb::Central.stubs(:sync_data_with_cartodb_central?).returns(false) @user = FactoryGirl.create(:carto_user) login_as(@user, scope: @user.username) host! "localhost.lan" cookies['_cartodb_base_url'] = 'prra-prra' get CartoDB.base_url(@user.username) + logout_path cookies['_cartodb_base_url'].should be_empty end end private def bypass_named_maps Carto::NamedMaps::Api.any_instance.stubs(show: nil, create: true, update: true, destroy: true) end end