# frozen_string_literal: true RSpec.describe ApplicationController do fab!(:user) context "for cache control headers" do it "sets the `no-cache, no-store` cache control response header when no error is raised" do get "/latest" expect(response.status).to eq(200) expect(response.headers["Cache-Control"]).to eq("no-cache, no-store") end it "sets the `no-cache, no-store` cache control response header when ActionController::RoutingError is raised" do get "/invalid-urlllllllllll" expect(response.status).to eq(404) expect(response.headers["Cache-Control"]).to eq("no-cache, no-store") end it "sets the `no-cache, no-store` cache control response header when Discourse::InvalidAccess is raised" do get "/latest.json", headers: { HTTP_API_KEY: "invalid-api-key" } expect(response.status).to eq(403) expect(response.headers["Cache-Control"]).to eq("no-cache, no-store") end end context "when visiting an invalid URL" do it "displays the not found page with the user's active theme" do theme = Fabricate(:theme, user_selectable: true) Fabricate(:theme_field, theme:, name: "header", value: "html") user.user_option.update!(theme_ids: [theme.id]) sign_in(user) get "/invalid-url" expect(response.status).to eq(404) expect(response.body).to include("html") expect(response.body).to include(I18n.t("page_not_found.title")) end end describe "#redirect_to_login_if_required" do let(:admin) { Fabricate(:admin) } before do admin # to skip welcome wizard at home page `/` SiteSetting.login_required = true end it "should never cache a login redirect" do get "/" expect(response.headers["Cache-Control"]).to eq("no-cache, no-store") end it "should not redirect to login" do get "/" expect(response).not_to redirect_to("/login") expect(response.status).to eq(200) end it "should redirect to SSO if enabled" do SiteSetting.discourse_connect_url = "http://someurl.com" SiteSetting.enable_discourse_connect = true get "/" expect(response).to redirect_to("/session/sso") end it "should redirect to authenticator if only one, and local logins disabled" do # Local logins and google enabled, show login UI SiteSetting.enable_google_oauth2_logins = true get "/" expect(response).not_to redirect_to("/login") expect(response.status).to eq(200) # Only google enabled, login immediately SiteSetting.enable_local_logins = false get "/" expect(response).to redirect_to("/auth/google_oauth2") # Google and GitHub enabled, direct to login UI SiteSetting.enable_github_logins = true get "/" expect(response).not_to redirect_to("/login") expect(response.status).to eq(200) end it "should not redirect to SSO when auth_immediately is disabled" do SiteSetting.auth_immediately = false SiteSetting.discourse_connect_url = "http://someurl.com" SiteSetting.enable_discourse_connect = true get "/" expect(response).not_to redirect_to("/login") expect(response.status).to eq(200) end it "should not redirect to authenticator when auth_immediately is disabled" do SiteSetting.auth_immediately = false SiteSetting.enable_google_oauth2_logins = true SiteSetting.enable_local_logins = false get "/" expect(response).not_to redirect_to("/login") expect(response.status).to eq(200) end context "with omniauth in test mode" do before do OmniAuth.config.test_mode = true OmniAuth.config.add_mock( :google_oauth2, info: OmniAuth::AuthHash::InfoHash.new(email: "address@example.com"), extra: { raw_info: OmniAuth::AuthHash.new(email_verified: true, email: "address@example.com"), }, ) Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2] end after do Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[ :google_oauth2 ] = nil OmniAuth.config.test_mode = false end it "should not redirect to authenticator if registration in progress" do SiteSetting.enable_local_logins = false SiteSetting.enable_google_oauth2_logins = true get "/" expect(response).to redirect_to("/auth/google_oauth2") expect(cookies[:authentication_data]).to eq(nil) get "/auth/google_oauth2/callback.json" expect(response).to redirect_to("/") expect(cookies[:authentication_data]).not_to eq(nil) get "/" expect(response).to redirect_to("/login") end end it "contains authentication data when cookies exist" do cookie_data = "someauthenticationdata" cookies["authentication_data"] = cookie_data get "/login" expect(response.status).to eq(200) expect(response.body).to include("data-authentication-data=\"#{cookie_data}\"") expect(response.headers["Set-Cookie"]).to include("authentication_data=;") # Delete cookie end it "deletes authentication data cookie even if already authenticated" do sign_in(Fabricate(:user)) cookies["authentication_data"] = "someauthenticationdata" get "/" expect(response.status).to eq(200) expect(response.body).not_to include("data-authentication-data=") expect(response.headers["Set-Cookie"]).to include("authentication_data=;") # Delete cookie end it "returns a 403 for json requests" do get "/latest" expect(response.status).to eq(302) get "/latest.json" expect(response.status).to eq(403) end end describe "#redirect_to_second_factor_if_required" do let(:admin) { Fabricate(:admin) } fab!(:user) { Fabricate(:user, refresh_auto_groups: true) } before do admin # to skip welcome wizard at home page `/` end it "should redirect admins when enforce_second_factor is 'all'" do SiteSetting.enforce_second_factor = "all" sign_in(admin) get "/" expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor") end it "should properly redirect admins when enforce_second_factor is 'all' in subfolder" do set_subfolder "/forum" SiteSetting.enforce_second_factor = "all" sign_in(admin) get "/" expect(response).to redirect_to("/forum/u/#{admin.username}/preferences/second-factor") end it "should redirect users when enforce_second_factor is 'all'" do SiteSetting.enforce_second_factor = "all" sign_in(user) get "/" expect(response).to redirect_to("/u/#{user.username}/preferences/second-factor") end it "should redirect users when enforce_second_factor is 'all' and authenticated via oauth" do SiteSetting.enforce_second_factor = "all" sign_in(user) user.user_auth_tokens.last.update(authenticated_with_oauth: true) get "/" expect(response).to redirect_to("/u/#{user.username}/preferences/second-factor") end it "should not redirect users when enforce_second_factor is 'all', authenticated via oauth but enforce_second_factor_on_external_auth is false" do SiteSetting.enforce_second_factor = "all" SiteSetting.enforce_second_factor_on_external_auth = false sign_in(user) user.user_auth_tokens.last.update(authenticated_with_oauth: true) get "/" expect(response.status).to eq(200) end it "should not redirect anonymous users when enforce_second_factor is 'all'" do SiteSetting.enforce_second_factor = "all" SiteSetting.allow_anonymous_mode = true sign_in(user) post "/u/toggle-anon.json" expect(response.status).to eq(200) get "/" expect(response.status).to eq(200) end it "should redirect admins when enforce_second_factor is 'staff'" do SiteSetting.enforce_second_factor = "staff" sign_in(admin) get "/" expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor") end it "should not redirect users when enforce_second_factor is 'staff'" do SiteSetting.enforce_second_factor = "staff" sign_in(user) get "/" expect(response.status).to eq(200) end it "should not redirect admins when turned off" do SiteSetting.enforce_second_factor = "no" sign_in(admin) get "/" expect(response.status).to eq(200) end it "should not redirect users when turned off" do SiteSetting.enforce_second_factor = "no" sign_in(user) get "/" expect(response.status).to eq(200) end it "correctly redirects for Unicode usernames" do SiteSetting.enforce_second_factor = "all" SiteSetting.unicode_usernames = true user = sign_in(Fabricate(:unicode_user)) get "/" expect(response).to redirect_to("/u/#{user.encoded_username}/preferences/second-factor") end context "when enforcing second factor for staff" do before do SiteSetting.enforce_second_factor = "staff" sign_in(admin) end context "when the staff member has not enabled TOTP or security keys" do it "redirects the staff to the second factor preferences" do get "/" expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor") end end context "when the staff member has enabled TOTP" do before { Fabricate(:user_second_factor_totp, user: admin) } it "does not redirects the staff to set up 2FA" do get "/" expect(response.status).to eq(200) end end context "when the staff member has enabled security keys" do before { Fabricate(:user_security_key_with_random_credential, user: admin) } it "does not redirects the staff to set up 2FA" do get "/" expect(response.status).to eq(200) end end end end describe "#redirect_to_profile_if_required" do fab!(:user) before { sign_in(user) } context "when the user is missing required custom fields" do before do Fabricate(:user_field, requirement: "for_all_users") UserRequiredFieldsVersion.create! end it "redirects the user to the profile preferences" do get "/hot" expect(response).to redirect_to("/u/#{user.username}/preferences/profile") end it "only logs user history once per day" do expect do RateLimiter.enable get "/hot" get "/hot" end.to change { UserHistory.count }.by(1) end end context "when the user has filled up all required custom fields" do before do Fabricate(:user_field, requirement: "for_all_users") UserRequiredFieldsVersion.create! user.bump_required_fields_version end it "redirects the user to the profile preferences" do get "/hot" expect(response).not_to redirect_to("/u/#{user.username}/preferences/profile") end end end describe "invalid request params" do let(:fake_logger) { FakeLogger.new } before { Rails.logger.broadcast_to(fake_logger) } after { Rails.logger.stop_broadcasting_to(fake_logger) } it "should not raise a 500 (nor should it log a warning) for bad params" do bad_str = (+"d\xDE").force_encoding("utf-8") expect(bad_str.valid_encoding?).to eq(false) get "/latest.json", params: { test: bad_str } expect(response.status).to eq(400) expect(fake_logger.warnings.length).to eq(0) expect(response.status).to eq(400) end end describe "missing required param" do it "should return a 400" do get "/search/query.json", params: { trem: "misspelled term" } expect(response.status).to eq(400) expect(response.parsed_body["errors"].first).to include( "param is missing or the value is empty or invalid: term", ) end end describe "build_not_found_page" do describe "topic not found" do it "should not redirect to permalink if topic/category does not exist" do topic = create_post.topic Permalink.create!(url: topic.relative_url, topic_id: topic.id + 1) topic.trash! SiteSetting.detailed_404 = false get topic.relative_url expect(response.status).to eq(404) SiteSetting.detailed_404 = true get topic.relative_url expect(response.status).to eq(410) end it "should return permalink for deleted topics" do topic = create_post.topic external_url = "https://somewhere.over.rainbow" Permalink.create!(url: topic.relative_url, external_url: external_url) topic.trash! get topic.relative_url expect(response.status).to eq(301) expect(response).to redirect_to(external_url) get "/t/#{topic.id}.json" expect(response.status).to eq(301) expect(response).to redirect_to(external_url) get "/t/#{topic.id}.json", xhr: true expect(response.status).to eq(200) expect(response.body).to eq(external_url) end it "supports subfolder with permalinks" do set_subfolder "/forum" trashed_topic = create_post.topic trashed_topic.trash! new_topic = create_post.topic permalink = Permalink.create!(url: trashed_topic.relative_url, topic_id: new_topic.id) # no subfolder because router doesn't know about subfolder in this test get "/t/#{trashed_topic.slug}/#{trashed_topic.id}" expect(response.status).to eq(301) expect(response).to redirect_to("/forum/t/#{new_topic.slug}/#{new_topic.id}") permalink.destroy category = Fabricate(:category) permalink = Permalink.create!(url: trashed_topic.relative_url, category_id: category.id) get "/t/#{trashed_topic.slug}/#{trashed_topic.id}" expect(response.status).to eq(301) expect(response).to redirect_to("/forum/c/#{category.slug}/#{category.id}") permalink.destroy permalink = Permalink.create!(url: trashed_topic.relative_url, post_id: new_topic.posts.last.id) get "/t/#{trashed_topic.slug}/#{trashed_topic.id}" expect(response.status).to eq(301) expect(response).to redirect_to( "/forum/t/#{new_topic.slug}/#{new_topic.id}/#{new_topic.posts.last.post_number}", ) end it "should return 404 and show Google search for an invalid topic route" do get "/t/nope-nope/99999999" expect(response.status).to eq(404) response_body = response.body expect(response_body).to include(I18n.t("page_not_found.search_button")) expect(response_body).to have_tag("input", with: { value: "nope nope" }) end it "should not include Google search if login_required is enabled" do SiteSetting.login_required = true sign_in(Fabricate(:user)) get "/t/nope-nope/99999999" expect(response.status).to eq(404) expect(response.body).to_not include("google.com/search") end it "should allow anchor tags in title" do TranslationOverride.upsert!( I18n.locale, "page_not_found.title", 'Visit search page', ) get "/t/nope-nope/99999999" expect(response.status).to eq(404) expect(response.body).to include('search') end it "should sanitize unsafe HTML in title" do TranslationOverride.upsert!( I18n.locale, "page_not_found.title", 'Page not found', ) get "/t/nope-nope/99999999" expect(response.status).to eq(404) expect(response.body).to_not include("