FIX: Hide delete button to invite as user are unable to delete anyway (#21884)

Moderators are not allowed to delete invites that don't belong to them
This commit is contained in:
Natalie Tay 2023-06-06 12:24:19 +08:00 committed by GitHub
parent 6642958706
commit d2ef490e9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 26 deletions

View File

@ -184,24 +184,26 @@
{{/if}} {{/if}}
</td> </td>
<td class="invite-actions"> {{#if invite.can_delete_invite}}
<DButton <td class="invite-actions">
@class="btn-default" <DButton
@icon="pencil-alt" @class="btn-default"
@action={{action "editInvite" invite}} @icon="pencil-alt"
@title="user.invited.edit" @action={{action "editInvite" invite}}
/> @title="user.invited.edit"
<DButton />
@icon="trash-alt" <DButton
@class="cancel" @icon="trash-alt"
@action={{action "destroyInvite" invite}} @class="cancel"
@title={{if @action={{action "destroyInvite" invite}}
invite.destroyed @title={{if
"user.invited.removed" invite.destroyed
"user.invited.remove" "user.invited.removed"
}} "user.invited.remove"
/> }}
</td> />
</td>
{{/if}}
</tr> </tr>
{{/each}} {{/each}}
</tbody> </tbody>

View File

@ -1,7 +1,6 @@
import { click, fillIn, visit } from "@ember/test-helpers"; import { click, fillIn, visit } from "@ember/test-helpers";
import { import {
acceptance, acceptance,
count,
exists, exists,
fakeTime, fakeTime,
loggedInUser, loggedInUser,
@ -57,15 +56,15 @@ acceptance("Invites - Create & Edit Invite Modal", function (needs) {
await visit("/u/eviltrout/invited/pending"); await visit("/u/eviltrout/invited/pending");
await click(".user-invite-buttons .btn:first-child"); await click(".user-invite-buttons .btn:first-child");
assert.ok(!exists("tbody tr"), "does not show invite before saving"); assert
.dom("table.user-invite-list tbody tr")
.exists({ count: 2 }, "is seeded with two rows");
await click(".btn-primary"); await click(".btn-primary");
assert.strictEqual( assert
count("tbody tr"), .dom("table.user-invite-list tbody tr")
1, .exists({ count: 3 }, "gets added to the list");
"adds invite to list after saving"
);
}); });
test("copying saves invite", async function (assert) { test("copying saves invite", async function (assert) {

View File

@ -224,7 +224,40 @@ export function applyDefaultHandlers(pretender) {
pretender.get("/u/eviltrout/invited.json", () => { pretender.get("/u/eviltrout/invited.json", () => {
return response({ return response({
invites: [], invites: [
{
id: 8,
invite_key: "hMFT8G1oKP",
link: "http://localhost:3000/invites/hMFT8G1oKP",
email: "steak@cat.com",
domain: null,
emailed: false,
can_delete_invite: true,
custom_message: null,
created_at: "2023-06-01T04:47:13.195Z",
updated_at: "2023-06-01T04:47:13.195Z",
expires_at: "2023-08-30T04:47:00.000Z",
expired: false,
topics: [],
groups: [],
},
{
id: 9,
invite_key: "hMFT8G1WHA",
link: "http://localhost:3000/invites/hMFT8G1WHA",
email: "tomtom@cat.com",
domain: null,
emailed: false,
can_delete_invite: false,
custom_message: null,
created_at: "2023-06-01T04:47:13.195Z",
updated_at: "2023-06-01T04:47:13.195Z",
expires_at: "2023-08-30T04:47:00.000Z",
expired: false,
topics: [],
groups: [],
},
],
can_see_invite_details: true, can_see_invite_details: true,
counts: { counts: {
pending: 0, pending: 0,

View File

@ -0,0 +1,19 @@
import { visit } from "@ember/test-helpers";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import { test } from "qunit";
acceptance("User invites", function (needs) {
needs.user();
test("hides delete button based on can_delete_invite", async function (assert) {
await visit("/u/eviltrout/invited");
assert.dom("table.user-invite-list tbody tr").exists({ count: 2 });
assert
.dom("table.user-invite-list tbody tr:nth-child(1) button.cancel")
.exists();
assert
.dom("table.user-invite-list tbody tr:nth-child(2) button.cancel")
.doesNotExist();
});
});

View File

@ -7,6 +7,7 @@ class InviteSerializer < ApplicationSerializer
:email, :email,
:domain, :domain,
:emailed, :emailed,
:can_delete_invite,
:max_redemptions_allowed, :max_redemptions_allowed,
:redemption_count, :redemption_count,
:custom_message, :custom_message,
@ -30,6 +31,10 @@ class InviteSerializer < ApplicationSerializer
object.emailed_status != Invite.emailed_status_types[:not_required] object.emailed_status != Invite.emailed_status_types[:not_required]
end end
def can_delete_invite
scope.is_admin? || object.invited_by_id == scope.current_user.id
end
def include_custom_message? def include_custom_message?
email.present? email.present?
end end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
RSpec.describe InviteSerializer do
describe "#can_delete_invite" do
fab!(:user) { Fabricate(:user) }
fab!(:admin) { Fabricate(:admin) }
fab!(:moderator) { Fabricate(:moderator) }
fab!(:invite_from_user) { Fabricate(:invite, invited_by: user) }
fab!(:invite_from_moderator) { Fabricate(:invite, invited_by: moderator) }
it "returns true for admin" do
serializer = InviteSerializer.new(invite_from_user, scope: Guardian.new(admin), root: false)
expect(serializer.as_json[:can_delete_invite]).to eq(true)
end
it "returns false for moderator" do
serializer =
InviteSerializer.new(invite_from_user, scope: Guardian.new(moderator), root: false)
expect(serializer.as_json[:can_delete_invite]).to eq(false)
end
it "returns true for inviter" do
serializer = InviteSerializer.new(invite_from_user, scope: Guardian.new(user), root: false)
expect(serializer.as_json[:can_delete_invite]).to eq(true)
end
it "returns false for plain user" do
serializer =
InviteSerializer.new(invite_from_moderator, scope: Guardian.new(user), root: false)
expect(serializer.as_json[:can_delete_invite]).to eq(false)
end
end
end