# 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("