2023-09-01 09:44:45 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2013-06-13 11:49:05 +02:00
|
|
|
#-- copyright
|
2020-01-15 11:31:26 +01:00
|
|
|
# OpenProject is an open source project management software.
|
2024-07-30 13:42:36 +02:00
|
|
|
# Copyright (C) the OpenProject GmbH
|
2013-06-13 11:49:05 +02:00
|
|
|
#
|
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
|
# modify it under the terms of the GNU General Public License version 3.
|
|
|
|
|
#
|
2013-09-16 17:59:31 +02:00
|
|
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
2021-01-13 17:47:45 +01:00
|
|
|
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
2013-09-16 17:59:31 +02:00
|
|
|
# Copyright (C) 2010-2013 the ChiliProject Team
|
|
|
|
|
#
|
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
|
# modify it under the terms of the GNU General Public License
|
|
|
|
|
# as published by the Free Software Foundation; either version 2
|
|
|
|
|
# of the License, or (at your option) any later version.
|
|
|
|
|
#
|
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
|
#
|
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
|
# along with this program; if not, write to the Free Software
|
|
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
#
|
2021-09-02 21:49:06 +02:00
|
|
|
# See COPYRIGHT and LICENSE files for more details.
|
2013-06-13 11:49:05 +02:00
|
|
|
#++
|
|
|
|
|
|
|
|
|
|
require "spec_helper"
|
|
|
|
|
|
2024-01-04 17:01:17 +01:00
|
|
|
RSpec.describe AccountController, :skip_2fa_stage do
|
2023-09-01 09:42:26 -05:00
|
|
|
let(:user_hook_class) do
|
|
|
|
|
Class.new(OpenProject::Hook::ViewListener) do
|
|
|
|
|
attr_reader :registered_user, :first_login_user
|
2019-09-27 09:26:28 +02:00
|
|
|
|
2023-09-01 09:42:26 -05:00
|
|
|
def user_registered(context)
|
|
|
|
|
@registered_user = context[:user]
|
|
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
|
2023-09-01 09:42:26 -05:00
|
|
|
def user_first_login(context)
|
|
|
|
|
@first_login_user = context[:user]
|
|
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
|
2023-09-01 09:42:26 -05:00
|
|
|
def reset!
|
|
|
|
|
@registered_user = nil
|
|
|
|
|
@first_login_user = nil
|
|
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-09-01 09:42:26 -05:00
|
|
|
let(:hook) { user_hook_class.instance }
|
2022-01-24 19:22:35 +01:00
|
|
|
let(:user) { build_stubbed(:user) }
|
2019-09-27 09:26:28 +02:00
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
hook.reset!
|
|
|
|
|
end
|
|
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
describe "GET #login" do
|
2018-02-13 07:52:58 +01:00
|
|
|
let(:params) { {} }
|
|
|
|
|
|
2023-09-01 10:06:33 -05:00
|
|
|
context "when the user is not already logged in" do
|
|
|
|
|
before do
|
|
|
|
|
get :login, params:
|
|
|
|
|
end
|
2018-02-13 07:52:58 +01:00
|
|
|
|
2023-09-01 10:06:33 -05:00
|
|
|
it "renders the view" do
|
|
|
|
|
expect(response).to render_template "login"
|
|
|
|
|
expect(response).to be_successful
|
|
|
|
|
end
|
2018-02-13 07:52:58 +01:00
|
|
|
end
|
|
|
|
|
|
2023-09-01 10:06:33 -05:00
|
|
|
context "when the user is already logged in" do
|
|
|
|
|
before do
|
|
|
|
|
login_as user
|
2018-02-13 07:52:58 +01:00
|
|
|
|
2023-09-01 10:06:33 -05:00
|
|
|
get :login, params:
|
|
|
|
|
end
|
2018-02-13 07:52:58 +01:00
|
|
|
|
|
|
|
|
it "redirects to home" do
|
|
|
|
|
expect(response)
|
2025-02-28 10:19:40 +01:00
|
|
|
.to redirect_to home_path
|
2018-02-13 07:52:58 +01:00
|
|
|
end
|
|
|
|
|
|
2023-09-01 10:06:33 -05:00
|
|
|
context "and a valid back url is present" do
|
|
|
|
|
let(:params) { { back_url: "/projects" } }
|
2018-02-13 07:52:58 +01:00
|
|
|
|
2023-09-01 10:06:33 -05:00
|
|
|
it "redirects to back_url value" do
|
|
|
|
|
expect(response)
|
|
|
|
|
.to redirect_to projects_path
|
|
|
|
|
end
|
2018-02-13 07:52:58 +01:00
|
|
|
end
|
|
|
|
|
|
2023-09-01 10:06:33 -05:00
|
|
|
context "and an invalid back url present" do
|
|
|
|
|
let(:params) { { back_url: "http://test.foo/work_packages/show/1" } }
|
2018-02-13 07:52:58 +01:00
|
|
|
|
2023-09-01 10:06:33 -05:00
|
|
|
it "redirects to home" do
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2023-09-01 10:06:33 -05:00
|
|
|
end
|
2018-02-13 07:52:58 +01:00
|
|
|
end
|
|
|
|
|
end
|
2017-11-21 08:22:03 +01:00
|
|
|
end
|
|
|
|
|
|
2023-04-26 13:09:51 +02:00
|
|
|
describe "GET #internal_login" do
|
2023-07-12 11:58:19 +02:00
|
|
|
shared_let(:admin) { create(:admin) }
|
|
|
|
|
|
2023-04-26 13:09:51 +02:00
|
|
|
context "when direct login enabled", with_config: { omniauth_direct_login_provider: "some_provider" } do
|
|
|
|
|
it "allows to login internally using a special route" do
|
|
|
|
|
get :internal_login
|
|
|
|
|
|
2023-07-12 11:58:19 +02:00
|
|
|
expect(response).to render_template "account/login"
|
2023-04-26 13:09:51 +02:00
|
|
|
end
|
|
|
|
|
|
2023-07-12 11:58:19 +02:00
|
|
|
it "allows to post to login" do
|
|
|
|
|
post :login, params: { username: admin.login, password: "adminADMIN!" }
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2023-04-26 13:09:51 +02:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when direct login disabled" do
|
2023-04-27 12:07:14 +02:00
|
|
|
it "the internal login route is inactive" do
|
2023-04-26 13:09:51 +02:00
|
|
|
get :internal_login
|
|
|
|
|
|
2023-04-26 14:36:52 +02:00
|
|
|
expect(response).to have_http_status(:not_found)
|
2023-04-26 13:09:51 +02:00
|
|
|
expect(session[:internal_login]).not_to be_present
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
describe "POST #login" do
|
2023-03-07 15:04:32 +01:00
|
|
|
shared_let(:admin) { create(:admin) }
|
2013-09-25 01:12:48 +02:00
|
|
|
|
2017-11-21 08:22:03 +01:00
|
|
|
describe "wrong password" do
|
|
|
|
|
it "redirects back to login" do
|
|
|
|
|
post :login, params: { username: "admin", password: "bad" }
|
2024-09-13 09:21:17 +02:00
|
|
|
expect(response).to have_http_status :unprocessable_entity
|
2017-11-21 08:22:03 +01:00
|
|
|
expect(response).to render_template "login"
|
|
|
|
|
expect(flash[:error]).to include "Invalid user or password"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2019-09-27 09:26:28 +02:00
|
|
|
context "with first login" do
|
|
|
|
|
before do
|
|
|
|
|
admin.update first_login: true
|
|
|
|
|
|
|
|
|
|
post :login, params: { username: admin.login, password: "adminADMIN!" }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "redirect to default path with ?first_time_user=true" do
|
|
|
|
|
expect(response).to redirect_to "/?first_time_user=true"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "calls the user_first_login hook" do
|
|
|
|
|
expect(hook.first_login_user).to eq admin
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "without first login" do
|
|
|
|
|
before do
|
|
|
|
|
post :login, params: { username: admin.login, password: "adminADMIN!" }
|
|
|
|
|
end
|
|
|
|
|
|
2025-02-28 10:19:40 +01:00
|
|
|
it "redirect to the home page" do
|
|
|
|
|
expect(response).to redirect_to home_path
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with login redirect set", with_settings: { after_login_default_redirect_url: "/my/page" } do
|
|
|
|
|
it "redirect to the my page" do
|
|
|
|
|
expect(response).to redirect_to my_page_path
|
|
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not call the user_first_login hook" do
|
|
|
|
|
expect(hook.first_login_user).to be_nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
describe "User logging in with back_url" do
|
|
|
|
|
it "redirects to a relative path" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: { username: admin.login, password: "adminADMIN!", back_url: "/" }
|
2014-04-11 16:10:39 +02:00
|
|
|
expect(response).to redirect_to root_path
|
2014-04-02 15:39:34 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "redirects to an absolute path given the same host" do
|
2014-04-02 15:39:34 +02:00
|
|
|
# note: test.host is the hostname during tests
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "http://test.host/work_packages/show/1"
|
|
|
|
|
}
|
2013-09-25 01:12:48 +02:00
|
|
|
expect(response).to redirect_to "/work_packages/show/1"
|
|
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "does not redirect to another host" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "http://test.foo/work_packages/show/1"
|
|
|
|
|
}
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2014-04-02 15:39:34 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "does not redirect to another host with a protocol relative url" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "//test.foo/fake"
|
|
|
|
|
}
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2013-09-25 01:12:48 +02:00
|
|
|
end
|
|
|
|
|
|
2015-04-07 14:16:00 +02:00
|
|
|
it "does not redirect to logout" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "/logout"
|
|
|
|
|
}
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2015-04-07 14:16:00 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-03 11:51:33 +02:00
|
|
|
context "with a relative url root" do
|
2023-09-01 10:07:15 -05:00
|
|
|
around do |example|
|
|
|
|
|
old_relative_url_root = OpenProject::Configuration["rails_relative_url_root"]
|
2015-09-13 19:56:12 +02:00
|
|
|
OpenProject::Configuration["rails_relative_url_root"] = "/openproject"
|
2023-09-01 10:07:15 -05:00
|
|
|
example.run
|
|
|
|
|
ensure
|
|
|
|
|
OpenProject::Configuration["rails_relative_url_root"] = old_relative_url_root
|
2014-04-03 11:51:33 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "redirects to the same subdirectory with an absolute path" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "http://test.host/openproject/work_packages/show/1"
|
|
|
|
|
}
|
2014-04-03 11:51:33 +02:00
|
|
|
expect(response).to redirect_to "/openproject/work_packages/show/1"
|
|
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "redirects to the same subdirectory with a relative path" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "/openproject/work_packages/show/1"
|
|
|
|
|
}
|
2014-04-03 11:51:33 +02:00
|
|
|
expect(response).to redirect_to "/openproject/work_packages/show/1"
|
|
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "does not redirect to another subdirectory with an absolute path" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "http://test.host/foo/work_packages/show/1"
|
|
|
|
|
}
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2014-04-03 11:51:33 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "does not redirect to another subdirectory with a relative path" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "/foo/work_packages/show/1"
|
|
|
|
|
}
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2014-04-03 11:51:33 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "does not redirect to another subdirectory by going up the path hierarchy" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "http://test.host/openproject/../foo/work_packages/show/1"
|
|
|
|
|
}
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2014-04-03 11:51:33 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "does not redirect to another subdirectory with a protocol relative path" do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
back_url: "//test.host/foo/work_packages/show/1"
|
|
|
|
|
}
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2014-04-03 11:51:33 +02:00
|
|
|
end
|
|
|
|
|
end
|
2013-09-25 01:12:48 +02:00
|
|
|
end
|
2014-05-14 09:53:25 +02:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
describe "GET #logout" do
|
2023-03-07 15:04:32 +01:00
|
|
|
shared_let(:admin) { create(:admin) }
|
2017-11-21 08:22:03 +01:00
|
|
|
|
|
|
|
|
it "calls reset_session" do
|
2023-09-01 10:17:43 -05:00
|
|
|
allow(controller).to receive(:reset_session)
|
2017-11-21 08:22:03 +01:00
|
|
|
login_as admin
|
2023-09-01 10:17:43 -05:00
|
|
|
|
2017-11-21 08:22:03 +01:00
|
|
|
get :logout
|
2023-09-01 10:17:43 -05:00
|
|
|
|
|
|
|
|
expect(controller).to have_received(:reset_session).once
|
2017-11-21 08:22:03 +01:00
|
|
|
expect(response).to be_redirect
|
|
|
|
|
end
|
2020-06-11 15:35:08 +02:00
|
|
|
|
|
|
|
|
context "with a user with an SSO provider attached" do
|
2025-04-04 09:10:51 +02:00
|
|
|
let(:user) { build_stubbed(:user, login: "bob", authentication_provider: sso_provider) }
|
2020-06-11 15:35:08 +02:00
|
|
|
let(:slo_callback) { nil }
|
|
|
|
|
let(:sso_provider) do
|
2023-04-26 13:09:51 +02:00
|
|
|
{ name: "saml", single_sign_out_callback: slo_callback }
|
2020-06-11 15:35:08 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
before do
|
2023-01-13 14:28:59 +01:00
|
|
|
allow(OpenProject::Plugins::AuthPlugin)
|
2020-06-11 15:35:08 +02:00
|
|
|
.to(receive(:login_provider_for))
|
|
|
|
|
.and_return(sso_provider)
|
|
|
|
|
login_as user
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with no provider" do
|
2024-01-04 17:01:17 +01:00
|
|
|
it "redirects to default" do
|
2020-06-11 15:35:08 +02:00
|
|
|
get :logout
|
|
|
|
|
expect(response).to redirect_to home_path
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with a redirecting callback" do
|
|
|
|
|
let(:slo_callback) do
|
|
|
|
|
Proc.new do |prev_session, prev_user|
|
|
|
|
|
if prev_session[:foo] && prev_user[:login] = "bob"
|
|
|
|
|
redirect_to "/login"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2020-06-15 13:01:50 +02:00
|
|
|
context "with direct login and redirecting callback",
|
2023-03-09 10:25:57 +01:00
|
|
|
with_config: { omniauth_direct_login_provider: "foo" }, with_settings: { login_required?: true } do
|
2024-01-04 17:01:17 +01:00
|
|
|
it "stills call the callback" do
|
2020-06-15 13:01:50 +02:00
|
|
|
# Set the previous session
|
|
|
|
|
session[:foo] = "bar"
|
|
|
|
|
|
|
|
|
|
get :logout
|
|
|
|
|
expect(response).to redirect_to "/login"
|
|
|
|
|
|
|
|
|
|
# Expect session to be cleared
|
|
|
|
|
expect(session[:foo]).to be_nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2024-01-04 17:01:17 +01:00
|
|
|
it "calls the callback" do
|
2020-06-11 15:35:08 +02:00
|
|
|
# Set the previous session
|
|
|
|
|
session[:foo] = "bar"
|
|
|
|
|
|
|
|
|
|
get :logout
|
|
|
|
|
expect(response).to redirect_to "/login"
|
|
|
|
|
|
|
|
|
|
# Expect session to be cleared
|
|
|
|
|
expect(session[:foo]).to be_nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with a no-op callback" do
|
2024-01-04 17:01:17 +01:00
|
|
|
it "redirects to default if the callback does nothing" do
|
2020-06-11 15:35:08 +02:00
|
|
|
was_called = false
|
2021-02-11 16:02:18 +01:00
|
|
|
sso_provider[:single_sign_out_callback] = Proc.new do
|
2020-06-11 15:35:08 +02:00
|
|
|
was_called = true
|
2021-02-11 16:02:18 +01:00
|
|
|
end
|
2020-06-11 15:35:08 +02:00
|
|
|
|
|
|
|
|
get :logout
|
|
|
|
|
expect(was_called).to be true
|
|
|
|
|
expect(response).to redirect_to home_path
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with a provider that does not have slo_callback" do
|
|
|
|
|
let(:slo_callback) { nil }
|
|
|
|
|
|
2024-01-04 17:01:17 +01:00
|
|
|
it "redirects to default if the callback does nothing" do
|
2020-06-11 15:35:08 +02:00
|
|
|
get :logout
|
|
|
|
|
expect(response).to redirect_to home_path
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2017-11-21 08:22:03 +01:00
|
|
|
end
|
|
|
|
|
|
2014-05-14 09:53:25 +02:00
|
|
|
describe "for a user trying to log in via an API request" do
|
|
|
|
|
before do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :login,
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "adminADMIN!"
|
|
|
|
|
},
|
|
|
|
|
format: :json
|
2014-05-14 09:53:25 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "returns a 410" do
|
|
|
|
|
expect(response.code.to_s).to eql("410")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not login the user" do
|
2023-09-01 10:12:33 -05:00
|
|
|
expect(controller.send(:current_user)).to be_anonymous
|
2014-05-14 09:53:25 +02:00
|
|
|
end
|
|
|
|
|
end
|
2014-07-17 14:26:37 +01:00
|
|
|
|
|
|
|
|
context "with disabled password login" do
|
|
|
|
|
before do
|
2014-08-07 11:25:52 +02:00
|
|
|
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(true)
|
2014-07-17 14:26:37 +01:00
|
|
|
|
|
|
|
|
post :login
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "is not found" do
|
2023-09-01 09:56:49 -05:00
|
|
|
expect(response).to have_http_status :not_found
|
2014-07-17 14:26:37 +01:00
|
|
|
end
|
|
|
|
|
end
|
2013-09-25 01:12:48 +02:00
|
|
|
end
|
|
|
|
|
|
2016-02-22 08:17:51 +01:00
|
|
|
describe "#login with omniauth_direct_login enabled",
|
2021-02-11 16:02:18 +01:00
|
|
|
with_config: { omniauth_direct_login_provider: "some_provider" } do
|
2014-07-15 15:32:38 +01:00
|
|
|
describe "GET" do
|
|
|
|
|
it "redirects to some_provider" do
|
|
|
|
|
get :login
|
|
|
|
|
|
|
|
|
|
expect(response).to redirect_to "/auth/some_provider"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "POST" do
|
2023-04-25 09:14:52 +02:00
|
|
|
shared_let(:admin) { create(:admin) }
|
|
|
|
|
|
2023-07-12 11:58:19 +02:00
|
|
|
it "allows to login internally still" do
|
2023-04-25 09:14:52 +02:00
|
|
|
post :login, params: { username: admin.login, password: "adminADMIN!" }
|
2025-02-28 10:19:40 +01:00
|
|
|
expect(response).to redirect_to home_path
|
2023-04-25 09:14:52 +02:00
|
|
|
end
|
2014-07-15 15:32:38 +01:00
|
|
|
end
|
|
|
|
|
end
|
2014-04-07 15:50:11 +02:00
|
|
|
|
2024-06-12 09:09:03 +01:00
|
|
|
describe "#login with omniauth_direct_login_provider set but empty",
|
|
|
|
|
with_config: { omniauth_direct_login_provider: "" } do
|
|
|
|
|
describe "GET" do
|
|
|
|
|
it "does not redirect to some_provider" do
|
|
|
|
|
get :login
|
|
|
|
|
|
|
|
|
|
expect(response).to have_http_status(:ok)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
describe "Login for user with forced password change" do
|
2022-01-24 19:22:35 +01:00
|
|
|
let(:admin) { create(:admin, force_password_change: true) }
|
2013-06-13 11:49:05 +02:00
|
|
|
|
|
|
|
|
before do
|
2023-09-01 10:33:05 -05:00
|
|
|
allow_any_instance_of(User).to receive(:change_password_allowed?).and_return(false) # rubocop:disable RSpec/AnyInstance
|
2013-06-13 11:49:05 +02:00
|
|
|
end
|
|
|
|
|
|
2019-02-27 07:43:23 +01:00
|
|
|
describe "Missing flash data for user initiated password change" do
|
|
|
|
|
before do
|
|
|
|
|
post "change_password",
|
|
|
|
|
flash: {
|
2025-11-25 11:50:11 +01:00
|
|
|
_password_change_user: nil
|
2019-02-27 07:43:23 +01:00
|
|
|
},
|
|
|
|
|
params: {
|
|
|
|
|
username: admin.login,
|
|
|
|
|
password: "whatever",
|
|
|
|
|
new_password: "whatever",
|
|
|
|
|
new_password_confirmation: "whatever2"
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "renders 404" do
|
2023-09-01 09:56:49 -05:00
|
|
|
expect(response).to have_http_status :not_found
|
2019-02-27 07:43:23 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2013-06-13 11:49:05 +02:00
|
|
|
describe "User who is not allowed to change password can't login" do
|
|
|
|
|
before do
|
2016-10-17 10:21:42 +02:00
|
|
|
post "change_password",
|
|
|
|
|
params: {
|
2025-11-25 11:50:11 +01:00
|
|
|
password_change_user: admin.login,
|
2016-10-17 10:21:42 +02:00
|
|
|
password: "adminADMIN!",
|
|
|
|
|
new_password: "adminADMIN!New",
|
|
|
|
|
new_password_confirmation: "adminADMIN!New"
|
|
|
|
|
}
|
2013-06-13 11:49:05 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "redirects to the login page" do
|
2013-08-30 17:27:22 +02:00
|
|
|
expect(response).to redirect_to "/login"
|
2013-06-13 11:49:05 +02:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
describe "User who is not allowed to change password, is not redirected to the login page" do
|
2013-06-13 11:49:05 +02:00
|
|
|
before do
|
2016-10-17 10:21:42 +02:00
|
|
|
post "login", params: { username: admin.login, password: "adminADMIN!" }
|
2013-06-13 11:49:05 +02:00
|
|
|
end
|
|
|
|
|
|
2021-01-22 09:14:21 -05:00
|
|
|
it "redirects to the login page" do
|
2013-08-30 17:27:22 +02:00
|
|
|
expect(response).to redirect_to "/login"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-07-17 10:57:44 +01:00
|
|
|
describe "POST #change_password" do
|
|
|
|
|
context "with disabled password login" do
|
|
|
|
|
before do
|
2014-08-07 11:25:52 +02:00
|
|
|
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(true)
|
2014-07-17 10:57:44 +01:00
|
|
|
post :change_password
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "is not found" do
|
2023-09-01 09:56:49 -05:00
|
|
|
expect(response).to have_http_status :not_found
|
2014-07-17 10:57:44 +01:00
|
|
|
end
|
|
|
|
|
end
|
2025-12-01 10:06:43 +01:00
|
|
|
|
|
|
|
|
context "with brute force protection",
|
|
|
|
|
with_settings: { brute_force_block_minutes: 30, brute_force_block_after_failed_logins: 20 } do
|
|
|
|
|
shared_let(:user) { create(:user, login: "testuser", password: "ValidPass123!", password_confirmation: "ValidPass123!") }
|
|
|
|
|
|
|
|
|
|
describe "blocks password change attempts after too many failures" do
|
|
|
|
|
before do
|
|
|
|
|
user.update_columns(
|
|
|
|
|
failed_login_count: 20,
|
|
|
|
|
last_failed_login_on: 1.minute.ago
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
post :change_password,
|
|
|
|
|
params: {
|
|
|
|
|
password_change_user: user.login,
|
|
|
|
|
password: "ValidPass123!",
|
|
|
|
|
new_password: "NewPass123!",
|
|
|
|
|
new_password_confirmation: "NewPass123!"
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "blocks the attempt even with correct password" do
|
|
|
|
|
expect(response).to have_http_status :unprocessable_entity
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not change the password" do
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.check_password?("ValidPass123!")).to be true
|
|
|
|
|
expect(user.check_password?("NewPass123!")).to be false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "shows an error message" do
|
|
|
|
|
if Setting.brute_force_block_after_failed_logins.to_i > 0
|
|
|
|
|
expected_message = I18n.t(:notice_account_invalid_credentials_or_blocked)
|
|
|
|
|
else
|
|
|
|
|
expected_message = I18n.t(:notice_account_invalid_credentials)
|
|
|
|
|
end
|
|
|
|
|
expect(flash[:error]).to eq(expected_message)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "logs failed password attempts" do
|
|
|
|
|
before do
|
|
|
|
|
user.update_columns(
|
|
|
|
|
failed_login_count: 0,
|
|
|
|
|
last_failed_login_on: nil
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
post :change_password,
|
|
|
|
|
params: {
|
|
|
|
|
password_change_user: user.login,
|
|
|
|
|
password: "WrongPassword!",
|
|
|
|
|
new_password: "NewPass123!",
|
|
|
|
|
new_password_confirmation: "NewPass123!"
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "increments failed login count" do
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.failed_login_count).to eq(1)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "updates last failed login timestamp" do
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.last_failed_login_on).to be_within(1.second).of(Time.zone.now)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not change the password" do
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.check_password?("ValidPass123!")).to be true
|
|
|
|
|
expect(user.check_password?("NewPass123!")).to be false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "accumulates multiple failed attempts" do
|
|
|
|
|
it "blocks after reaching the threshold" do
|
|
|
|
|
user.update_columns(
|
|
|
|
|
failed_login_count: 0,
|
|
|
|
|
last_failed_login_on: nil
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Make 20 failed attempts
|
|
|
|
|
20.times do
|
|
|
|
|
post :change_password,
|
|
|
|
|
params: {
|
|
|
|
|
password_change_user: user.login,
|
|
|
|
|
password: "WrongPassword!",
|
|
|
|
|
new_password: "NewPass123!",
|
|
|
|
|
new_password_confirmation: "NewPass123!"
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.failed_login_count).to eq(20)
|
|
|
|
|
|
|
|
|
|
# Next attempt should be blocked even with correct password
|
|
|
|
|
post :change_password,
|
|
|
|
|
params: {
|
|
|
|
|
password_change_user: user.login,
|
|
|
|
|
password: "ValidPass123!",
|
|
|
|
|
new_password: "NewPass123!",
|
|
|
|
|
new_password_confirmation: "NewPass123!"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.check_password?("ValidPass123!")).to be true
|
|
|
|
|
expect(user.check_password?("NewPass123!")).to be false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "resets failed login count on successful password change" do
|
|
|
|
|
before do
|
|
|
|
|
user.update_columns(
|
|
|
|
|
failed_login_count: 5,
|
|
|
|
|
last_failed_login_on: 1.minute.ago
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
post :change_password,
|
|
|
|
|
params: {
|
|
|
|
|
password_change_user: user.login,
|
|
|
|
|
password: "ValidPass123!",
|
|
|
|
|
new_password: "NewPass123!",
|
|
|
|
|
new_password_confirmation: "NewPass123!"
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "resets the failed login count to zero" do
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.failed_login_count).to eq(0)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "changes the password successfully" do
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.check_password?("NewPass123!")).to be true
|
|
|
|
|
expect(user.check_password?("ValidPass123!")).to be false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
describe "allows password change after block time expires" do
|
|
|
|
|
before do
|
|
|
|
|
user.update_columns(
|
|
|
|
|
failed_login_count: 20,
|
|
|
|
|
last_failed_login_on: 31.minutes.ago
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
post :change_password,
|
|
|
|
|
params: {
|
|
|
|
|
password_change_user: user.login,
|
|
|
|
|
password: "ValidPass123!",
|
|
|
|
|
new_password: "NewPass123!",
|
|
|
|
|
new_password_confirmation: "NewPass123!"
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "allows the password change" do
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.check_password?("NewPass123!")).to be true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "resets the failed login count" do
|
|
|
|
|
user.reload
|
|
|
|
|
expect(user.failed_login_count).to eq(0)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2014-07-17 10:57:44 +01:00
|
|
|
end
|
|
|
|
|
|
2023-09-01 11:59:47 -05:00
|
|
|
describe "POST #lost_password" do
|
|
|
|
|
context "when the user has been invited but not yet activated" do
|
|
|
|
|
shared_let(:admin) { create(:admin, status: :invited) }
|
|
|
|
|
shared_let(:token) { create(:recovery_token, user: admin) }
|
|
|
|
|
|
|
|
|
|
context "with a valid token" do
|
|
|
|
|
before do
|
|
|
|
|
post :lost_password, params: { token: token.value }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "redirects to the login page" do
|
|
|
|
|
expect(response).to redirect_to "/login"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
shared_examples "registration disabled" do
|
|
|
|
|
it "redirects to back the login page" do
|
|
|
|
|
expect(response).to redirect_to signin_path
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "informs the user that registration is disabled" do
|
|
|
|
|
expect(flash[:error]).to eq(I18n.t("account.error_self_registration_disabled"))
|
|
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
|
|
|
|
|
it "does not call the user_registered callback" do
|
|
|
|
|
expect(hook.registered_user).to be_nil
|
|
|
|
|
end
|
2014-07-18 09:44:32 +01:00
|
|
|
end
|
|
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
describe "GET #register" do
|
2022-10-26 10:57:56 +02:00
|
|
|
context "with self registration on",
|
|
|
|
|
with_settings: { self_registration: Setting::SelfRegistration.automatic } do
|
2014-07-18 09:44:32 +01:00
|
|
|
context "and password login enabled" do
|
|
|
|
|
before do
|
|
|
|
|
get :register
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "is successful" do
|
2014-08-07 11:02:07 +02:00
|
|
|
expect(subject).to respond_with :success
|
2014-07-18 09:44:32 +01:00
|
|
|
expect(response).to render_template :register
|
|
|
|
|
expect(assigns[:user]).not_to be_nil
|
2022-08-07 19:29:35 +02:00
|
|
|
expect(assigns[:user].notification_settings.size).to eq(1)
|
2014-07-18 09:44:32 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "and password login disabled" do
|
|
|
|
|
before do
|
2014-08-07 11:25:52 +02:00
|
|
|
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(true)
|
2014-07-18 09:44:32 +01:00
|
|
|
|
|
|
|
|
get :register
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it_behaves_like "registration disabled"
|
2013-08-30 17:27:22 +02:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-10-26 10:57:56 +02:00
|
|
|
context "with self registration off",
|
|
|
|
|
with_settings: { self_registration: Setting::SelfRegistration.disabled } do
|
2013-08-30 17:27:22 +02:00
|
|
|
before do
|
|
|
|
|
get :register
|
|
|
|
|
end
|
|
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
it_behaves_like "registration disabled"
|
2013-08-30 17:27:22 +02:00
|
|
|
end
|
2017-06-01 14:19:45 +01:00
|
|
|
|
2022-10-26 10:57:56 +02:00
|
|
|
context "with self registration off but an ongoing invitation activation",
|
|
|
|
|
with_settings: { self_registration: Setting::SelfRegistration.disabled } do
|
2023-03-07 15:04:32 +01:00
|
|
|
let(:token) { create(:invitation_token) }
|
2017-06-01 14:19:45 +01:00
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
session[:invitation_token] = token.value
|
|
|
|
|
|
|
|
|
|
get :register
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "is successful" do
|
|
|
|
|
expect(subject).to respond_with :success
|
|
|
|
|
expect(response).to render_template :register
|
|
|
|
|
expect(assigns[:user]).not_to be_nil
|
|
|
|
|
end
|
|
|
|
|
end
|
2013-08-30 17:27:22 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# See integration/account_test.rb for the full test
|
2023-09-01 09:52:40 -05:00
|
|
|
describe "POST #register" do
|
2022-10-26 10:57:56 +02:00
|
|
|
context "with self registration on automatic",
|
|
|
|
|
with_settings: { self_registration: Setting::SelfRegistration.automatic } do
|
2013-08-30 17:27:22 +02:00
|
|
|
before do
|
2016-03-10 21:40:12 +00:00
|
|
|
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(false)
|
2013-08-30 17:27:22 +02:00
|
|
|
end
|
|
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
context "with password login enabled" do
|
2015-11-16 16:20:46 +00:00
|
|
|
# expects `redirect_to_path`
|
|
|
|
|
shared_examples "automatic self registration succeeds" do
|
|
|
|
|
before do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :register,
|
|
|
|
|
params: {
|
|
|
|
|
user: {
|
|
|
|
|
login: "register",
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
password_confirmation: "adminADMIN!",
|
|
|
|
|
firstname: "John",
|
|
|
|
|
lastname: "Doe",
|
|
|
|
|
mail: "register@example.com"
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-11-16 16:20:46 +00:00
|
|
|
end
|
|
|
|
|
|
2019-09-27 09:26:28 +02:00
|
|
|
it "redirects to the expected path" do
|
2015-11-16 16:20:46 +00:00
|
|
|
expect(subject).to respond_with :redirect
|
|
|
|
|
expect(assigns[:user]).not_to be_nil
|
|
|
|
|
expect(subject).to redirect_to(redirect_to_path)
|
|
|
|
|
expect(User.where(login: "register").last).not_to be_nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "set the user status to active" do
|
|
|
|
|
user = User.where(login: "register").last
|
|
|
|
|
expect(user).not_to be_nil
|
2021-02-04 09:52:56 +01:00
|
|
|
expect(user).to be_active
|
2015-11-16 16:20:46 +00:00
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
|
|
|
|
|
it "calls the user_registered callback" do
|
|
|
|
|
user = hook.registered_user
|
|
|
|
|
|
|
|
|
|
expect(user.mail).to eq "register@example.com"
|
|
|
|
|
expect(user).to be_active
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it_behaves_like "automatic self registration succeeds" do
|
|
|
|
|
let(:redirect_to_path) { "/?first_time_user=true" }
|
|
|
|
|
|
|
|
|
|
it "calls the user_first_login callback" do
|
|
|
|
|
user = hook.first_login_user
|
|
|
|
|
|
|
|
|
|
expect(user.mail).to eq "register@example.com"
|
|
|
|
|
end
|
2014-07-18 09:44:32 +01:00
|
|
|
end
|
2018-06-06 10:39:04 +01:00
|
|
|
|
|
|
|
|
context "with user limit reached" do
|
2023-03-07 15:04:32 +01:00
|
|
|
let!(:admin) { create(:admin) }
|
2018-06-20 12:05:22 +01:00
|
|
|
|
|
|
|
|
let(:params) do
|
|
|
|
|
{
|
|
|
|
|
user: {
|
|
|
|
|
login: "register",
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
password_confirmation: "adminADMIN!",
|
|
|
|
|
firstname: "John",
|
|
|
|
|
lastname: "Doe",
|
|
|
|
|
mail: "register@example.com"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
2018-06-06 10:39:04 +01:00
|
|
|
before do
|
|
|
|
|
allow(OpenProject::Enterprise).to receive(:user_limit_reached?).and_return(true)
|
2018-06-20 12:05:22 +01:00
|
|
|
|
2018-07-10 16:50:11 +02:00
|
|
|
post :register, params:
|
2018-06-06 10:39:04 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "fails" do
|
|
|
|
|
expect(subject).to redirect_to(signin_path)
|
|
|
|
|
|
|
|
|
|
expect(flash[:error]).to match /user limit reached/
|
|
|
|
|
end
|
2018-06-20 12:05:22 +01:00
|
|
|
|
|
|
|
|
it "notifies the admins about the issue" do
|
2019-10-11 12:55:34 +02:00
|
|
|
perform_enqueued_jobs
|
|
|
|
|
|
2023-09-01 10:13:08 -05:00
|
|
|
mail = ActionMailer::Base.deliveries.detect { |m| m.to.first == admin.mail }
|
2020-04-09 11:54:26 +02:00
|
|
|
expect(mail).to be_present
|
2018-06-20 12:05:22 +01:00
|
|
|
expect(mail.subject).to match /limit reached/
|
|
|
|
|
expect(mail.body.parts.first.to_s).to match /new user \(#{params[:user][:mail]}\)/
|
|
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
|
|
|
|
|
it "does not call the user_registered callback" do
|
|
|
|
|
expect(hook.registered_user).to be_nil
|
|
|
|
|
end
|
2018-06-06 10:39:04 +01:00
|
|
|
end
|
2013-08-30 17:27:22 +02:00
|
|
|
end
|
|
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
context "with password login disabled" do
|
|
|
|
|
before do
|
2014-08-07 11:25:52 +02:00
|
|
|
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(true)
|
2014-07-18 09:44:32 +01:00
|
|
|
|
|
|
|
|
post :register
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it_behaves_like "registration disabled"
|
2013-08-30 17:27:22 +02:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-10-26 10:57:56 +02:00
|
|
|
context "with self registration by email",
|
|
|
|
|
with_settings: { self_registration: Setting::SelfRegistration.by_email } do
|
2014-07-18 09:44:32 +01:00
|
|
|
context "with password login enabled" do
|
|
|
|
|
before do
|
2017-11-06 10:42:52 +01:00
|
|
|
Token::Invitation.delete_all
|
2016-10-17 10:21:42 +02:00
|
|
|
post :register,
|
|
|
|
|
params: {
|
|
|
|
|
user: {
|
|
|
|
|
login: "register",
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
password_confirmation: "adminADMIN!",
|
|
|
|
|
firstname: "John",
|
|
|
|
|
lastname: "Doe",
|
|
|
|
|
mail: "register@example.com"
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-07-18 09:44:32 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "redirects to the login page" do
|
2014-08-07 11:02:07 +02:00
|
|
|
expect(subject).to redirect_to "/login"
|
2014-07-18 09:44:32 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "doesn't activate the user but sends out a token instead" do
|
2024-08-06 10:29:28 +02:00
|
|
|
expect(User.find_by_login("register")).not_to be_active
|
2017-11-07 10:00:32 +01:00
|
|
|
token = Token::Invitation.last
|
2014-07-18 09:44:32 +01:00
|
|
|
expect(token.user.mail).to eq("register@example.com")
|
|
|
|
|
expect(token).not_to be_expired
|
|
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
|
|
|
|
|
it "calls the user_registered callback" do
|
|
|
|
|
user = hook.registered_user
|
|
|
|
|
|
|
|
|
|
expect(user.mail).to eq "register@example.com"
|
|
|
|
|
expect(user).to be_registered
|
|
|
|
|
end
|
2013-09-03 10:29:50 +02:00
|
|
|
end
|
|
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
context "with password login disabled" do
|
|
|
|
|
before do
|
2014-08-07 11:25:52 +02:00
|
|
|
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(true)
|
2014-07-18 09:44:32 +01:00
|
|
|
|
|
|
|
|
post :register
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it_behaves_like "registration disabled"
|
2013-09-03 10:29:50 +02:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2022-10-26 10:57:56 +02:00
|
|
|
context "with manual activation",
|
|
|
|
|
with_settings: { self_registration: Setting::SelfRegistration.manual } do
|
2014-04-11 10:04:39 +02:00
|
|
|
let(:user_hash) do
|
2014-11-03 21:12:15 +01:00
|
|
|
{ login: "register",
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
password_confirmation: "adminADMIN!",
|
|
|
|
|
firstname: "John",
|
|
|
|
|
lastname: "Doe",
|
|
|
|
|
mail: "register@example.com" }
|
2013-09-03 10:29:50 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
context "without back_url" do
|
2014-04-11 10:04:39 +02:00
|
|
|
before do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :register, params: { user: user_hash }
|
2014-04-11 10:04:39 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
it "redirects to the login page" do
|
2014-04-11 10:04:39 +02:00
|
|
|
expect(response).to redirect_to "/login"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "doesn't activate the user" do
|
2024-08-06 10:29:28 +02:00
|
|
|
expect(User.find_by_login("register")).not_to be_active
|
2014-04-11 10:04:39 +02:00
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
|
|
|
|
|
it "calls the user_registered callback" do
|
|
|
|
|
user = hook.registered_user
|
|
|
|
|
|
|
|
|
|
expect(user.mail).to eq "register@example.com"
|
|
|
|
|
expect(user).to be_registered
|
|
|
|
|
end
|
2014-04-11 10:04:39 +02:00
|
|
|
end
|
|
|
|
|
|
2014-04-14 18:16:11 +02:00
|
|
|
context "with back_url" do
|
2014-04-11 10:04:39 +02:00
|
|
|
before do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :register, params: { user: user_hash, back_url: "https://example.net/some_back_url" }
|
2014-04-11 10:04:39 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "preserves the back url" do
|
2023-04-26 14:35:35 +02:00
|
|
|
expect(response).to redirect_to("/login?back_url=https%3A%2F%2Fexample.net%2Fsome_back_url")
|
2014-04-11 10:04:39 +02:00
|
|
|
end
|
2019-09-27 09:26:28 +02:00
|
|
|
|
|
|
|
|
it "calls the user_registered callback" do
|
|
|
|
|
user = hook.registered_user
|
|
|
|
|
|
|
|
|
|
expect(user.mail).to eq "register@example.com"
|
|
|
|
|
expect(user).to be_registered
|
|
|
|
|
end
|
2013-09-03 10:29:50 +02:00
|
|
|
end
|
2014-07-18 09:44:32 +01:00
|
|
|
|
|
|
|
|
context "with password login disabled" do
|
|
|
|
|
before do
|
2014-08-07 11:25:52 +02:00
|
|
|
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(true)
|
2014-07-18 09:44:32 +01:00
|
|
|
|
|
|
|
|
post :register
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it_behaves_like "registration disabled"
|
|
|
|
|
end
|
2013-09-03 10:29:50 +02:00
|
|
|
end
|
|
|
|
|
|
2022-10-26 10:57:56 +02:00
|
|
|
context "with self registration off",
|
|
|
|
|
with_settings: { self_registration: Setting::SelfRegistration.disabled } do
|
2013-08-30 17:27:22 +02:00
|
|
|
before do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :register,
|
|
|
|
|
params: {
|
|
|
|
|
user: {
|
|
|
|
|
login: "register",
|
|
|
|
|
password: "adminADMIN!",
|
|
|
|
|
password_confirmation: "adminADMIN!",
|
|
|
|
|
firstname: "John",
|
|
|
|
|
lastname: "Doe",
|
|
|
|
|
mail: "register@example.com"
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-08-30 17:27:22 +02:00
|
|
|
end
|
|
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
it_behaves_like "registration disabled"
|
2013-06-13 11:49:05 +02:00
|
|
|
end
|
2013-09-03 10:29:50 +02:00
|
|
|
|
2022-10-26 10:57:56 +02:00
|
|
|
context "with on-the-fly registration",
|
|
|
|
|
with_settings: { self_registration: Setting::SelfRegistration.disabled } do
|
2013-09-03 10:29:50 +02:00
|
|
|
before do
|
2023-09-01 10:33:05 -05:00
|
|
|
allow_any_instance_of(User).to receive(:change_password_allowed?).and_return(false) # rubocop:disable RSpec/AnyInstance
|
2013-09-03 10:29:50 +02:00
|
|
|
end
|
|
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
context "with password login disabled" do
|
|
|
|
|
before do
|
2014-08-07 11:25:52 +02:00
|
|
|
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(true)
|
2014-07-18 09:44:32 +01:00
|
|
|
end
|
2013-09-03 10:29:50 +02:00
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
describe "registration" do
|
|
|
|
|
before do
|
2016-10-17 10:21:42 +02:00
|
|
|
post :register,
|
|
|
|
|
params: {
|
|
|
|
|
user: {
|
|
|
|
|
firstname: "Foo",
|
|
|
|
|
lastname: "Smith",
|
|
|
|
|
mail: "foo@bar.com"
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-07-18 09:44:32 +01:00
|
|
|
end
|
2014-04-07 15:50:11 +02:00
|
|
|
|
2014-07-18 09:44:32 +01:00
|
|
|
it_behaves_like "registration disabled"
|
|
|
|
|
end
|
2013-09-03 10:29:50 +02:00
|
|
|
end
|
|
|
|
|
end
|
2013-06-13 11:49:05 +02:00
|
|
|
end
|
2017-08-30 15:17:02 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
describe "POST #activate" do
|
|
|
|
|
describe "account activation" do
|
|
|
|
|
shared_examples "account activation" do
|
|
|
|
|
let(:token) { Token::Invitation.create user: }
|
|
|
|
|
|
|
|
|
|
let(:activation_params) do
|
|
|
|
|
{
|
|
|
|
|
token: token.value
|
|
|
|
|
}
|
|
|
|
|
end
|
2018-06-07 10:25:04 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
context "with an expired token" do
|
|
|
|
|
before do
|
|
|
|
|
token.update_column :expires_on, 1.day.ago
|
2018-06-07 10:25:04 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
post :activate, params: activation_params
|
|
|
|
|
end
|
2018-06-07 10:25:04 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
it "fails and shows an expiration warning" do
|
|
|
|
|
expect(subject).to redirect_to("/")
|
|
|
|
|
expect(flash[:warning]).to include "expired"
|
|
|
|
|
end
|
2018-06-07 10:25:04 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
it "deletes the old token and generates a new one" do
|
|
|
|
|
old_token = Token::Invitation.find_by(id: token.id)
|
|
|
|
|
new_token = Token::Invitation.find_by(user_id: token.user.id)
|
|
|
|
|
|
|
|
|
|
expect(old_token).to be_nil
|
|
|
|
|
expect(new_token).to be_present
|
2018-06-07 10:25:04 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
expect(new_token).not_to be_expired
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "sends out a new activation email" do
|
|
|
|
|
new_token = Token::Invitation.find_by(user_id: token.user.id)
|
|
|
|
|
|
|
|
|
|
perform_enqueued_jobs
|
|
|
|
|
|
|
|
|
|
mail = ActionMailer::Base.deliveries.last
|
|
|
|
|
expect(mail.parts.first.body.raw_source).to include "activate?token=#{new_token.value}"
|
|
|
|
|
end
|
|
|
|
|
end
|
2018-06-07 10:25:04 +01:00
|
|
|
end
|
2018-06-20 12:05:22 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
context "with an invited user" do
|
|
|
|
|
it_behaves_like "account activation" do
|
|
|
|
|
let(:user) { create(:user, status: 4) }
|
|
|
|
|
end
|
|
|
|
|
end
|
2019-10-11 12:55:34 +02:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
context "with a registered user" do
|
|
|
|
|
it_behaves_like "account activation" do
|
|
|
|
|
let(:user) { create(:user, status: 2) }
|
|
|
|
|
end
|
2018-06-20 12:05:22 +01:00
|
|
|
end
|
2018-06-07 10:25:04 +01:00
|
|
|
end
|
|
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
describe "user limit" do
|
|
|
|
|
let!(:admin) { create(:admin) }
|
|
|
|
|
let(:user) { create(:user, status:) }
|
|
|
|
|
let(:status) { -1 }
|
2018-06-07 10:25:04 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
let(:token) { Token::Invitation.create!(user_id: user.id) }
|
2018-06-07 10:25:04 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
before do
|
|
|
|
|
allow(OpenProject::Enterprise).to receive(:user_limit_reached?).and_return(true)
|
2018-06-07 10:25:04 +01:00
|
|
|
|
2023-09-01 09:52:40 -05:00
|
|
|
post :activate, params: { token: token.value }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
shared_examples "activation is blocked due to user limit" do
|
|
|
|
|
it "does not activate the user" do
|
|
|
|
|
expect(user.reload).not_to be_active
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "redirects back to the login page and shows the user limit error" do
|
|
|
|
|
expect(response).to redirect_to(signin_path)
|
|
|
|
|
expect(flash[:error]).to match /user limit reached.*contact.*admin/i
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "notifies the admins about the issue" do
|
|
|
|
|
perform_enqueued_jobs
|
|
|
|
|
|
|
|
|
|
mail = ActionMailer::Base.deliveries.detect { |m| m.to.first == admin.mail }
|
|
|
|
|
expect(mail).to be_present
|
|
|
|
|
expect(mail.subject).to match /limit reached/
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with an invited user" do
|
|
|
|
|
let(:status) { User.statuses[:invited] }
|
|
|
|
|
|
|
|
|
|
it_behaves_like "activation is blocked due to user limit"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with a registered user" do
|
|
|
|
|
let(:status) { User.statuses[:registered] }
|
|
|
|
|
|
|
|
|
|
it_behaves_like "activation is blocked due to user limit"
|
|
|
|
|
end
|
2018-06-07 10:25:04 +01:00
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2017-08-30 15:17:02 +01:00
|
|
|
describe "GET #auth_source_sso_failed (/sso)" do
|
2018-07-10 16:50:11 +02:00
|
|
|
render_views
|
2018-02-13 07:52:58 +01:00
|
|
|
|
2017-08-30 15:17:02 +01:00
|
|
|
let(:failure) do
|
|
|
|
|
{
|
2022-01-10 15:59:24 +01:00
|
|
|
login:,
|
2017-08-30 15:17:02 +01:00
|
|
|
back_url: "/my/account",
|
|
|
|
|
ttl: 1
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
|
2023-07-26 08:17:18 +02:00
|
|
|
let(:ldap_auth_source) { create(:ldap_auth_source) }
|
|
|
|
|
let(:user) { create(:user, status: 2, ldap_auth_source:) }
|
2022-01-10 15:59:24 +01:00
|
|
|
let(:login) { user.login }
|
2017-08-30 15:17:02 +01:00
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
session[:auth_source_sso_failure] = failure
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with a non-active user" do
|
|
|
|
|
it "shows the non-active error message" do
|
|
|
|
|
get :auth_source_sso_failed
|
|
|
|
|
|
|
|
|
|
expect(session[:auth_source_sso_failure]).not_to be_present
|
|
|
|
|
|
|
|
|
|
expect(response.body)
|
|
|
|
|
.to have_text "Your account has not yet been activated."
|
|
|
|
|
expect(response.body)
|
|
|
|
|
.to have_text "Single Sign-On (SSO) for user '#{user.login}' failed"
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "with an invalid user" do
|
2023-03-07 15:04:32 +01:00
|
|
|
let!(:duplicate) { create(:user, mail: "login@DerpLAP.net") }
|
2022-01-10 15:59:24 +01:00
|
|
|
let(:login) { "foo" }
|
|
|
|
|
let(:attrs) do
|
|
|
|
|
{ mail: duplicate.mail, login:, firstname: "bla", lastname: "bar" }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
before do
|
2023-11-01 16:32:29 +01:00
|
|
|
allow(LdapAuthSource).to receive(:get_user_attributes).and_return attrs
|
2017-08-30 15:17:02 +01:00
|
|
|
end
|
|
|
|
|
|
2022-01-10 16:36:59 +01:00
|
|
|
it "shows the account creation form with an error" do
|
2017-08-30 15:17:02 +01:00
|
|
|
get :auth_source_sso_failed
|
|
|
|
|
|
|
|
|
|
expect(session[:auth_source_sso_failure]).not_to be_present
|
|
|
|
|
|
|
|
|
|
expect(response.body).to have_text "Create a new account"
|
|
|
|
|
expect(response.body).to have_text "This field is invalid: Email has already been taken."
|
|
|
|
|
end
|
|
|
|
|
end
|
2022-01-10 15:59:24 +01:00
|
|
|
|
|
|
|
|
context "with a missing email" do
|
2023-03-07 15:04:32 +01:00
|
|
|
let!(:duplicate) { create(:user, mail: "login@DerpLAP.net") }
|
2022-01-10 15:59:24 +01:00
|
|
|
let(:login) { "foo" }
|
|
|
|
|
let(:attrs) do
|
|
|
|
|
{ login:, firstname: "bla", lastname: "bar" }
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
before do
|
2023-11-01 16:32:29 +01:00
|
|
|
allow(LdapAuthSource).to receive(:get_user_attributes).and_return attrs
|
2022-01-10 15:59:24 +01:00
|
|
|
end
|
|
|
|
|
|
2022-01-10 16:36:59 +01:00
|
|
|
it "shows the account creation form with an error" do
|
2022-01-10 15:59:24 +01:00
|
|
|
get :auth_source_sso_failed
|
|
|
|
|
|
|
|
|
|
expect(session[:auth_source_sso_failure]).not_to be_present
|
|
|
|
|
|
|
|
|
|
expect(response.body).to have_text "Create a new account"
|
|
|
|
|
expect(response.body).to have_text "This field is invalid: Email can't be blank."
|
|
|
|
|
end
|
|
|
|
|
end
|
2017-08-30 15:17:02 +01:00
|
|
|
end
|
2024-08-29 13:57:50 +02:00
|
|
|
|
|
|
|
|
describe "registering through auth source" do
|
|
|
|
|
context "when not providing all required fields" do
|
2025-05-21 08:53:20 +02:00
|
|
|
let(:slug) { "google" }
|
|
|
|
|
let(:omniauth_strategy) { double("Google Strategy", name: slug) } # rubocop:disable RSpec/VerifiedDoubles
|
|
|
|
|
let!(:oidc_google) { create(:oidc_provider_google, slug:) }
|
2024-08-29 13:57:50 +02:00
|
|
|
let(:omniauth_hash) do
|
|
|
|
|
OmniAuth::AuthHash.new(
|
2025-05-21 08:53:20 +02:00
|
|
|
provider: slug,
|
2024-08-29 13:57:50 +02:00
|
|
|
strategy: omniauth_strategy,
|
|
|
|
|
uid: "123545",
|
|
|
|
|
info: { name: "foo",
|
|
|
|
|
email: "foo@bar.com",
|
|
|
|
|
first_name: "foo",
|
|
|
|
|
last_name: "bar" }
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
before do
|
|
|
|
|
request.env["omniauth.auth"] = omniauth_hash
|
|
|
|
|
request.env["omniauth.strategy"] = omniauth_strategy
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "registers user via post" do
|
|
|
|
|
allow(OpenProject::OmniAuth::Authorization).to receive(:after_login!)
|
|
|
|
|
|
|
|
|
|
auth_source_registration = omniauth_hash.merge(
|
|
|
|
|
omniauth: true,
|
|
|
|
|
timestamp: Time.current
|
|
|
|
|
)
|
|
|
|
|
session[:auth_source_registration] = auth_source_registration
|
|
|
|
|
post :register,
|
|
|
|
|
params: {
|
|
|
|
|
user: {
|
|
|
|
|
login: "login@bar.com",
|
|
|
|
|
firstname: "Foo",
|
|
|
|
|
lastname: "Smith",
|
|
|
|
|
mail: "foo@bar.com"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
expect(response).to redirect_to home_url(first_time_user: true)
|
|
|
|
|
|
|
|
|
|
user = User.find_by_login("login@bar.com")
|
|
|
|
|
expect(OpenProject::OmniAuth::Authorization)
|
|
|
|
|
.to have_received(:after_login!).with(user, a_hash_including(omniauth_hash), any_args)
|
|
|
|
|
expect(user).to be_an_instance_of(User)
|
|
|
|
|
expect(user.ldap_auth_source_id).to be_nil
|
|
|
|
|
expect(user.current_password).to be_nil
|
|
|
|
|
expect(user.identity_url).to eql("google:123545")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
context "when after a timeout expired" do
|
|
|
|
|
before do
|
|
|
|
|
session[:auth_source_registration] = omniauth_hash.merge(
|
|
|
|
|
omniauth: true,
|
|
|
|
|
timestamp: 42.days.ago
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
it "does not register the user when providing all the missing fields" do
|
|
|
|
|
post :register,
|
|
|
|
|
params: {
|
|
|
|
|
user: {
|
|
|
|
|
firstname: "Foo",
|
|
|
|
|
lastname: "Smith",
|
|
|
|
|
mail: "foo@bar.com"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(response).to redirect_to signin_path
|
|
|
|
|
expect(flash[:error]).to eq(I18n.t(:error_omniauth_registration_timed_out))
|
|
|
|
|
expect(User.find_by_login("foo@bar.com")).to be_nil
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
2013-06-13 11:49:05 +02:00
|
|
|
end
|