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

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

Also displays the user’s last payload in the admin UI to help with debugging SSO issues.

diff --git a/app/assets/javascripts/admin/controllers/admin-user-index.js b/app/assets/javascripts/admin/controllers/admin-user-index.js
index 92101bc..8f58755 100644
--- a/app/assets/javascripts/admin/controllers/admin-user-index.js
+++ b/app/assets/javascripts/admin/controllers/admin-user-index.js
@@ -132,6 +132,11 @@ export default Controller.extend(CanCheckEmails, {
       .catch(() => bootbox.alert(I18n.t("generic_error")));
   },
 
+  @discourseComputed("model.single_sign_on_record.last_payload")
+  ssoPayload(lastPayload) {
+    return lastPayload.split("&");
+  },
+
   actions: {
     impersonate() {
       return this.model.impersonate();
@@ -321,5 +326,16 @@ export default Controller.extend(CanCheckEmails, {
     resetPrimaryGroup() {
       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();
+        }
+      );
+    },
   },
 });
diff --git a/app/assets/javascripts/admin/models/admin-user.js b/app/assets/javascripts/admin/models/admin-user.js
index 5882910..8d4b38b 100644
--- a/app/assets/javascripts/admin/models/admin-user.js
+++ b/app/assets/javascripts/admin/models/admin-user.js
@@ -567,6 +567,16 @@ const AdminUser = User.extend({
   _formatError(event) {
     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({
diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs
index 5cccf43..c043ce5 100644
--- a/app/assets/javascripts/admin/templates/user-index.hbs
+++ b/app/assets/javascripts/admin/templates/user-index.hbs
@@ -652,6 +652,16 @@
       <div class="display-row">
         <div class="field">{{i18n "admin.user.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 class="display-row">
         <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="value">{{sso.external_avatar_url}}</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}}
   </section>
 {{/if}}
diff --git a/app/assets/stylesheets/common/admin/users.scss b/app/assets/stylesheets/common/admin/users.scss
index dcc0b0e..28a8612 100644
--- a/app/assets/stylesheets/common/admin/users.scss
+++ b/app/assets/stylesheets/common/admin/users.scss
@@ -49,6 +49,7 @@
     max-width: 350px;
     min-width: 50px;
     margin-left: 12px;
+    word-break: break-word;
     .select-kit {
       min-width: 100px;
     }
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 0deed95..48b292c 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -23,7 +23,8 @@ class Admin::UsersController < Admin::AdminController
                                     :merge,
                                     :reset_bounce_score,
                                     :disable_second_factor,
-                                    :delete_posts_batch]
+                                    :delete_posts_batch,
+                                    :sso_record]
 
   def index
     users = ::AdminUserIndexQuery.new(params).find_users
@@ -498,6 +499,12 @@ class Admin::UsersController < Admin::AdminController
     render json: success_json
   end
 
+  def sso_record
+    guardian.ensure_can_delete_sso_record!(@user)
+    @user.single_sign_on_record.destroy!
+    render json: success_json
+  end
+
   private
 
   def perform_post_action
diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb
index 0f3840e..6dc9413 100644
--- a/app/serializers/admin_detailed_user_serializer.rb
+++ b/app/serializers/admin_detailed_user_serializer.rb
@@ -31,6 +31,7 @@ class AdminDetailedUserSerializer < AdminUserSerializer
              :can_view_action_logs,
              :second_factor_enabled,
              :can_disable_second_factor,
+             :can_delete_sso_record,
              :api_key_count
 
   has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
@@ -126,4 +127,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer
   def api_key_count
     object.api_keys.active.count
   end
+
+  def can_delete_sso_record
+    scope.can_delete_sso_record?(object)
+  end
 end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index e630d46..6e38785 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -4626,6 +4626,9 @@ en:
           external_name: "Name"
           external_email: "Email"
           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:
         title: "User Fields"
diff --git a/config/routes.rb b/config/routes.rb
index 47bc641..342ced7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -144,6 +144,7 @@ Discourse::Application.routes.draw do
         post "merge"
         post "reset_bounce_score"
         put "disable_second_factor"
+        delete "sso_record"
       end
       get "users/:id.json" => 'users#show', defaults: { format: 'json' }
       get 'users/:id/:username' => 'users#show', constraints: { username: RouteFormat.username }
diff --git a/lib/guardian/user_guardian.rb b/lib/guardian/user_guardian.rb
index aa91779..6e2ccdb 100644
--- a/lib/guardian/user_guardian.rb
+++ b/lib/guardian/user_guardian.rb
@@ -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?
   end
 
+  def can_delete_sso_record?(user)
+    SiteSetting.enable_sso && user && is_admin?
+  end
 end
diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb
index 14a6cb0..83bd456 100644
--- a/spec/requests/admin/users_controller_spec.rb
+++ b/spec/requests/admin/users_controller_spec.rb
@@ -1049,4 +1049,17 @@ RSpec.describe Admin::UsersController do
     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

GitHub sha: 273db57d

This commit appears in #10669 which was approved by eviltrout. It was merged by pmusaraj.