diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index fe6ee5d7595..3875747d5e5 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -168,6 +168,14 @@ class UsersController < ApplicationController attributes[:user_associated_accounts] = [] params[:external_ids].each do |provider_name, provider_uid| + if provider_name == 'discourse_connect' + raise Discourse::InvalidParameters.new(:external_ids) unless SiteSetting.enable_discourse_connect + + attributes[:discourse_connect] = { external_id: provider_uid } + + next + end + authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name } raise Discourse::InvalidParameters.new(:external_ids) if !authenticator&.is_managed? diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index 72f934802fc..dccdf49daee 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -203,6 +203,10 @@ class UserUpdater update_allowed_pm_users(attributes[:allowed_pm_usernames]) end + if attributes.key?(:discourse_connect) + update_discourse_connect(attributes[:discourse_connect]) + end + if attributes.key?(:user_associated_accounts) updated_associated_accounts(attributes[:user_associated_accounts]) end @@ -354,6 +358,17 @@ class UserUpdater end end + def update_discourse_connect(discourse_connect) + external_id = discourse_connect[:external_id] + sso = SingleSignOnRecord.find_or_initialize_by(user_id: user.id) + + if external_id.present? + sso.update!(external_id: discourse_connect[:external_id], last_payload: "external_id=#{discourse_connect[:external_id]}") + else + sso.destroy! + end + end + attr_reader :user, :guardian def format_url(website) diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 38dd9e189c9..1d926afff87 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -2426,6 +2426,8 @@ RSpec.describe UsersController do before do DiscoursePluginRegistry.register_auth_provider(plugin_auth_provider) + SiteSetting.discourse_connect_url = 'http://localhost' + SiteSetting.enable_discourse_connect = true end after do @@ -2463,6 +2465,75 @@ RSpec.describe UsersController do expect(user.reload.user_associated_account_ids).to be_blank end + it 'can create SingleSignOnRecord records' do + params = { + external_ids: { discourse_connect: 'discourse_connect_uid' }, + } + + expect { put "/u/#{user.username}.json", params: params, headers: { HTTP_API_KEY: api_key.key } } + .to change { SingleSignOnRecord.count }.by(1) + + expect(response.status).to eq(200) + + single_sign_on_record = SingleSignOnRecord.last + expect(user.reload.single_sign_on_record).to eq(single_sign_on_record) + expect(single_sign_on_record.external_id).to eq('discourse_connect_uid') + end + + it 'can update SingleSignOnRecord records' do + user = Fabricate(:user) + SingleSignOnRecord.create!(user_id: user.id, external_id: 'discourse_connect_uid', last_payload: 'discourse_connect_uid') + + params = { + external_ids: { discourse_connect: 'discourse_connect_uid_2' }, + } + + expect { put "/u/#{user.username}.json", params: params, headers: { HTTP_API_KEY: api_key.key } } + .not_to change { SingleSignOnRecord.count } + + expect(response.status).to eq(200) + expect(user.reload.single_sign_on_record.external_id).to eq('discourse_connect_uid_2') + end + + it 'can delete SingleSignOnRecord records' do + user = Fabricate(:user) + SingleSignOnRecord.create!(user_id: user.id, external_id: 'discourse_connect_uid', last_payload: 'discourse_connect_uid') + + params = { + external_ids: { discourse_connect: nil }, + } + + expect { put "/u/#{user.username}.json", params: params, headers: { HTTP_API_KEY: api_key.key } } + .to change { SingleSignOnRecord.count }.by(-1) + + expect(response.status).to eq(200) + expect(user.reload.single_sign_on_record).to be_blank + end + + it 'can update SingleSignOnRecord and UserAssociatedAccount records in a single call' do + user = Fabricate(:user) + user.user_associated_accounts.create!(provider_name: 'pluginauth', provider_uid: 'pluginauth_uid') + SingleSignOnRecord.create!(user_id: user.id, external_id: 'discourse_connect_uid', last_payload: 'discourse_connect_uid') + + params = { + external_ids: { + discourse_connect: 'discourse_connect_uid_2', + pluginauth: 'pluginauth_uid_2' + }, + } + + expect { put "/u/#{user.username}.json", params: params, headers: { HTTP_API_KEY: api_key.key } } + .to change { SingleSignOnRecord.count + UserAssociatedAccount.count }.by(0) + + expect(response.status).to eq(200) + expect(user.reload.single_sign_on_record.external_id).to eq('discourse_connect_uid_2') + user_associated_account = UserAssociatedAccount.last + expect(user.reload.user_associated_account_ids).to contain_exactly(user_associated_account.id) + expect(user_associated_account.provider_name).to eq('pluginauth') + expect(user_associated_account.provider_uid).to eq('pluginauth_uid_2') + expect(user_associated_account.user_id).to eq(user.id) + end + it 'returns error if external ID provider does not exist' do params = { external_ids: { 'pluginauth2' => 'pluginauth_uid' },