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}}
</td>
<td class="invite-actions">
<DButton
@class="btn-default"
@icon="pencil-alt"
@action={{action "editInvite" invite}}
@title="user.invited.edit"
/>
<DButton
@icon="trash-alt"
@class="cancel"
@action={{action "destroyInvite" invite}}
@title={{if
invite.destroyed
"user.invited.removed"
"user.invited.remove"
}}
/>
</td>
{{#if invite.can_delete_invite}}
<td class="invite-actions">
<DButton
@class="btn-default"
@icon="pencil-alt"
@action={{action "editInvite" invite}}
@title="user.invited.edit"
/>
<DButton
@icon="trash-alt"
@class="cancel"
@action={{action "destroyInvite" invite}}
@title={{if
invite.destroyed
"user.invited.removed"
"user.invited.remove"
}}
/>
</td>
{{/if}}
</tr>
{{/each}}
</tbody>

View File

@ -1,7 +1,6 @@
import { click, fillIn, visit } from "@ember/test-helpers";
import {
acceptance,
count,
exists,
fakeTime,
loggedInUser,
@ -57,15 +56,15 @@ acceptance("Invites - Create & Edit Invite Modal", function (needs) {
await visit("/u/eviltrout/invited/pending");
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");
assert.strictEqual(
count("tbody tr"),
1,
"adds invite to list after saving"
);
assert
.dom("table.user-invite-list tbody tr")
.exists({ count: 3 }, "gets added to the list");
});
test("copying saves invite", async function (assert) {

View File

@ -224,7 +224,40 @@ export function applyDefaultHandlers(pretender) {
pretender.get("/u/eviltrout/invited.json", () => {
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,
counts: {
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,
:domain,
:emailed,
:can_delete_invite,
:max_redemptions_allowed,
:redemption_count,
:custom_message,
@ -30,6 +31,10 @@ class InviteSerializer < ApplicationSerializer
object.emailed_status != Invite.emailed_status_types[:not_required]
end
def can_delete_invite
scope.is_admin? || object.invited_by_id == scope.current_user.id
end
def include_custom_message?
email.present?
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