FEATURE: Allow admins to delete user SSO records in the UI ()

Also displays the user's last payload in the admin UI to help with debugging SSO issues.
This commit is contained in:
Penar Musaraj 2020-09-15 10:00:10 -04:00 committed by GitHub
parent 6d7b8a71c0
commit 273db57d6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 80 additions and 1 deletions
app
assets
javascripts/admin
stylesheets/common/admin
controllers/admin
serializers
config
lib/guardian
spec/requests/admin

@ -132,6 +132,11 @@ export default Controller.extend(CanCheckEmails, {
.catch(() => bootbox.alert(I18n.t("generic_error"))); .catch(() => bootbox.alert(I18n.t("generic_error")));
}, },
@discourseComputed("model.single_sign_on_record.last_payload")
ssoPayload(lastPayload) {
return lastPayload.split("&");
},
actions: { actions: {
impersonate() { impersonate() {
return this.model.impersonate(); return this.model.impersonate();
@ -321,5 +326,16 @@ export default Controller.extend(CanCheckEmails, {
resetPrimaryGroup() { resetPrimaryGroup() {
this.set("model.primary_group_id", this.originalPrimaryGroupId); this.set("model.primary_group_id", this.originalPrimaryGroupId);
}, },
deleteSSORecord() {
return bootbox.confirm(
I18n.t("admin.user.sso.confirm_delete"),
I18n.t("no_value"),
I18n.t("yes_value"),
() => {
return this.model.deleteSSORecord();
}
);
},
}, },
}); });

@ -567,6 +567,16 @@ const AdminUser = User.extend({
_formatError(event) { _formatError(event) {
return `http: ${event.status} - ${event.body}`; return `http: ${event.status} - ${event.body}`;
}, },
deleteSSORecord() {
return ajax(`/admin/users/${this.id}/sso_record.json`, {
type: "DELETE",
})
.then(() => {
this.set("single_sign_on_record", null);
})
.catch(popupAjaxError);
},
}); });
AdminUser.reopenClass({ AdminUser.reopenClass({

@ -652,6 +652,16 @@
<div class="display-row"> <div class="display-row">
<div class="field">{{i18n "admin.user.sso.external_id"}}</div> <div class="field">{{i18n "admin.user.sso.external_id"}}</div>
<div class="value">{{sso.external_id}}</div> <div class="value">{{sso.external_id}}</div>
{{#if model.can_delete_sso_record}}
<div class="controls">
{{d-button
class="btn-danger"
action=(action "deleteSSORecord")
icon="far-trash-alt"
label="admin.user.sso.delete_sso_record"
}}
</div>
{{/if}}
</div> </div>
<div class="display-row"> <div class="display-row">
<div class="field">{{i18n "admin.user.sso.external_username"}}</div> <div class="field">{{i18n "admin.user.sso.external_username"}}</div>
@ -671,6 +681,16 @@
<div class="field">{{i18n "admin.user.sso.external_avatar_url"}}</div> <div class="field">{{i18n "admin.user.sso.external_avatar_url"}}</div>
<div class="value">{{sso.external_avatar_url}}</div> <div class="value">{{sso.external_avatar_url}}</div>
</div> </div>
{{#if sso.last_payload}}
<div class="display-row">
<div class="field">{{i18n "admin.user.sso.last_payload"}}</div>
<div class="value">
{{#each ssoPayload as |line|}}
{{line}}<br>
{{/each}}
</div>
</div>
{{/if}}
{{/with}} {{/with}}
</section> </section>
{{/if}} {{/if}}

@ -49,6 +49,7 @@
max-width: 350px; max-width: 350px;
min-width: 50px; min-width: 50px;
margin-left: 12px; margin-left: 12px;
word-break: break-word;
.select-kit { .select-kit {
min-width: 100px; min-width: 100px;
} }

@ -23,7 +23,8 @@ class Admin::UsersController < Admin::AdminController
:merge, :merge,
:reset_bounce_score, :reset_bounce_score,
:disable_second_factor, :disable_second_factor,
:delete_posts_batch] :delete_posts_batch,
:sso_record]
def index def index
users = ::AdminUserIndexQuery.new(params).find_users users = ::AdminUserIndexQuery.new(params).find_users
@ -498,6 +499,12 @@ class Admin::UsersController < Admin::AdminController
render json: success_json render json: success_json
end end
def sso_record
guardian.ensure_can_delete_sso_record!(@user)
@user.single_sign_on_record.destroy!
render json: success_json
end
private private
def perform_post_action def perform_post_action

@ -31,6 +31,7 @@ class AdminDetailedUserSerializer < AdminUserSerializer
:can_view_action_logs, :can_view_action_logs,
:second_factor_enabled, :second_factor_enabled,
:can_disable_second_factor, :can_disable_second_factor,
:can_delete_sso_record,
:api_key_count :api_key_count
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
@ -126,4 +127,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer
def api_key_count def api_key_count
object.api_keys.active.count object.api_keys.active.count
end end
def can_delete_sso_record
scope.can_delete_sso_record?(object)
end
end end

@ -4626,6 +4626,9 @@ en:
external_name: "Name" external_name: "Name"
external_email: "Email" external_email: "Email"
external_avatar_url: "Profile Picture URL" external_avatar_url: "Profile Picture URL"
last_payload: "Last Payload"
delete_sso_record: "Delete SSO Record"
confirm_delete: "Are you sure you would like to delete this single sign on (SSO) record?"
user_fields: user_fields:
title: "User Fields" title: "User Fields"

@ -144,6 +144,7 @@ Discourse::Application.routes.draw do
post "merge" post "merge"
post "reset_bounce_score" post "reset_bounce_score"
put "disable_second_factor" put "disable_second_factor"
delete "sso_record"
end end
get "users/:id.json" => 'users#show', defaults: { format: 'json' } get "users/:id.json" => 'users#show', defaults: { format: 'json' }
get 'users/:id/:username' => 'users#show', constraints: { username: RouteFormat.username } get 'users/:id/:username' => 'users#show', constraints: { username: RouteFormat.username }

@ -167,4 +167,7 @@ module UserGuardian
(is_me?(user) && user.has_trust_level?(SiteSetting.min_trust_level_to_allow_user_card_background.to_i)) || is_staff? (is_me?(user) && user.has_trust_level?(SiteSetting.min_trust_level_to_allow_user_card_background.to_i)) || is_staff?
end end
def can_delete_sso_record?(user)
SiteSetting.enable_sso && user && is_admin?
end
end end

@ -1049,4 +1049,17 @@ RSpec.describe Admin::UsersController do
end end
end end
describe '#sso_record' do
fab!(:sso_record) { SingleSignOnRecord.create!(user_id: user.id, external_id: '12345', external_email: user.email, last_payload: '') }
it "deletes the record" do
SiteSetting.sso_url = "https://www.example.com/sso"
SiteSetting.enable_sso = true
delete "/admin/users/#{user.id}/sso_record.json"
expect(response.status).to eq(200)
expect(user.single_sign_on_record).to eq(nil)
end
end
end end