SECURITY: When enabled only allow Discourse Connect logins

If Discourse Connect is enabled no other methods for account creation or
authentication should be allowed.
This commit is contained in:
Blake Erickson 2024-10-24 13:06:55 -06:00 committed by =
parent 15b43a205b
commit 17bdffc900
9 changed files with 84 additions and 0 deletions

View File

@ -324,6 +324,8 @@ class InvitesController < ApplicationController
}, },
) )
raise Discourse::NotFound if SiteSetting.enable_discourse_connect
invite = Invite.find_by(invite_key: params[:id]) invite = Invite.find_by(invite_key: params[:id])
redeeming_user = current_user redeeming_user = current_user

View File

@ -110,6 +110,9 @@ class Users::OmniauthCallbacksController < ApplicationController
end end
def self.find_authenticator(name) def self.find_authenticator(name)
if SiteSetting.enable_discourse_connect
raise Discourse::InvalidAccess.new(I18n.t("authenticator_not_found"))
end
Discourse.enabled_authenticators.each do |authenticator| Discourse.enabled_authenticators.each do |authenticator|
return authenticator if authenticator.name == name return authenticator if authenticator.name == name
end end

View File

@ -664,6 +664,9 @@ class UsersController < ApplicationController
params.permit(:user_fields) params.permit(:user_fields)
params.permit(:external_ids) params.permit(:external_ids)
if SiteSetting.enable_discourse_connect && !is_api?
return fail_with("login.new_registrations_disabled_discourse_connect")
end
return fail_with("login.new_registrations_disabled") unless SiteSetting.allow_new_registrations return fail_with("login.new_registrations_disabled") unless SiteSetting.allow_new_registrations
if params[:password] && params[:password].length > User.max_password_length if params[:password] && params[:password].length > User.max_password_length

View File

@ -2975,6 +2975,7 @@ en:
omniauth_confirm_button: "Continue" omniauth_confirm_button: "Continue"
authenticator_error_no_valid_email: "No email addresses associated with %{account} are allowed. You may need to configure your account with a different email address." authenticator_error_no_valid_email: "No email addresses associated with %{account} are allowed. You may need to configure your account with a different email address."
new_registrations_disabled: "New account registrations are not allowed at this time." new_registrations_disabled: "New account registrations are not allowed at this time."
new_registrations_disabled_discourse_connect: "New account registrations are only allowed through Discourse Connect."
password_too_long: "Passwords are limited to 200 characters." password_too_long: "Passwords are limited to 200 characters."
email_too_long: "The email you provided is too long. Mailbox names must be no more than 254 characters, and domain names must be no more than 253 characters." email_too_long: "The email you provided is too long. Mailbox names must be no more than 254 characters, and domain names must be no more than 253 characters."
wrong_invite_code: "The invite code you entered was incorrect." wrong_invite_code: "The invite code you entered was incorrect."

View File

@ -183,4 +183,23 @@ describe "GitHub Oauth2" do
expect(response.location).to eq("http://test.localhost/") expect(response.location).to eq("http://test.localhost/")
expect(session[:current_user_id]).to eq(user1.id) expect(session[:current_user_id]).to eq(user1.id)
end end
it "doesn't log in the user if discourse connect is enabled" do
SiteSetting.discourse_connect_url = "https://example.com/sso"
SiteSetting.enable_discourse_connect = true
post "/auth/github"
expect(response.status).to eq(302)
expect(response.location).to start_with("https://github.com/login/oauth/authorize?")
setup_github_emails_stub(
[
{ email: user1.email, primary: true, verified: true, visibility: "private" },
{ email: user2.email, primary: false, verified: true, visibility: "private" },
],
)
post "/auth/github/callback", params: { state: session["omniauth.state"], code: temp_code }
expect(response.status).to eq(403)
expect(session[:current_user_id]).to be_blank
end
end end

View File

@ -106,6 +106,21 @@ describe "Twitter OAuth 1.0a" do
expect(session[:current_user_id]).to eq(user1.id) expect(session[:current_user_id]).to eq(user1.id)
end end
it "doesn't sign in the user discourse connect is enabled" do
SiteSetting.discourse_connect_url = "https://example.com/sso"
SiteSetting.enable_discourse_connect = true
post "/auth/twitter"
expect(response.status).to eq(302)
expect(response.location).to start_with("https://api.twitter.com/oauth/authenticate")
setup_twitter_email_stub(email: user1.email)
post "/auth/twitter/callback", params: { state: session["omniauth.state"] }
expect(response.status).to eq(403)
expect(session[:current_user_id]).to be_blank
end
it "doesn't sign in anyone if the API response from twitter doesn't include an email (implying the user's email on twitter isn't verified)" do it "doesn't sign in anyone if the API response from twitter doesn't include an email (implying the user's email on twitter isn't verified)" do
post "/auth/twitter" post "/auth/twitter"
expect(response.status).to eq(302) expect(response.status).to eq(302)

View File

@ -966,6 +966,14 @@ RSpec.describe InvitesController do
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
it "fails when discourse connect is enabled" do
SiteSetting.discourse_connect_url = "https://example.com/sso"
SiteSetting.enable_discourse_connect = true
put "/invites/show/#{invite.invite_key}.json"
expect(response.status).to eq(404)
end
context "with OmniAuth provider" do context "with OmniAuth provider" do
fab!(:authenticated_email) { "test@example.com" } fab!(:authenticated_email) { "test@example.com" }

View File

@ -3230,6 +3230,22 @@ RSpec.describe SessionController do
expect(session[:current_user_id]).to eq(nil) expect(session[:current_user_id]).to eq(nil)
end end
it "fails when discourse connect is enabled" do
SiteSetting.discourse_connect_url = "https://www.example.com/sso"
SiteSetting.enable_discourse_connect = true
simulate_localhost_passkey_challenge
user.activate
user.create_or_fetch_secure_identifier
post "/session/passkey/auth.json",
params: {
publicKeyCredential:
valid_passkey_auth_data.merge(
{ userHandle: Base64.strict_encode64(user.secure_identifier) },
),
}
expect(response.status).to eq(403)
end
it "logs the user in" do it "logs the user in" do
simulate_localhost_passkey_challenge simulate_localhost_passkey_challenge
user.activate user.activate

View File

@ -785,6 +785,23 @@ RSpec.describe UsersController do
end end
end end
context "with discourse connect enabled" do
before do
SiteSetting.discourse_connect_url = "http://example.com/sso"
SiteSetting.enable_discourse_connect = true
end
it "blocks registration for local logins" do
SiteSetting.enable_local_logins = true
post_user
response_body = response.parsed_body
expect(response_body["message"]).to eq(
"New account registrations are only allowed through Discourse Connect.",
)
end
end
context "with local logins disabled" do context "with local logins disabled" do
before do before do
SiteSetting.enable_local_logins = false SiteSetting.enable_local_logins = false