FEATURE: API to update user's discourse connect external id (#19085)

* FEATURE: API to update user's discourse connect external id

This adds a special handling of updates to DiscourseConnect external_id
in the general user update API endpoint.

Admins can create, update or delete a user SingleSignOn record using

PUT /u/:username.json
{
  "external_ids": {
    "discourse_connect": "new-external-id"
  }
}
This commit is contained in:
Rafael dos Santos Silva 2022-11-18 11:37:21 -03:00 committed by GitHub
parent 3ec7b2a769
commit 86bf46a24b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 0 deletions

View File

@ -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?

View File

@ -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)

View File

@ -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' },