mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(api): make an empty admin guild patch apply no change (#2538)
This commit is contained in:
@@ -2772,7 +2772,7 @@
|
||||
"content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
|
||||
}
|
||||
},
|
||||
"description": "Partially updates a guild. The permissions required are selected by the fields present in the body and are evaluated with all-of semantics: name requires GUILD_UPDATE_NAME, vanity_url_code requires GUILD_UPDATE_VANITY, new_owner_id requires GUILD_TRANSFER_OWNERSHIP, add_features and remove_features require GUILD_UPDATE_FEATURES, and fields together with every other setting requires GUILD_UPDATE_SETTINGS. A body carrying no field requires the wildcard permission. Every applied change is logged to the audit log.",
|
||||
"description": "Partially updates a guild. The permissions required are selected by the fields present in the body and are evaluated with all-of semantics: name requires GUILD_UPDATE_NAME, vanity_url_code requires GUILD_UPDATE_VANITY, new_owner_id requires GUILD_TRANSFER_OWNERSHIP, add_features and remove_features require GUILD_UPDATE_FEATURES, and fields together with every other setting requires GUILD_UPDATE_SETTINGS. A body with no fields applies no change. Every applied change is logged to the audit log.",
|
||||
"security": [{"adminApiKey": []}],
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -72,7 +72,10 @@ function selectGuildUpdateACLs(body: UpdateGuildRequest): Array<string> {
|
||||
if (body.new_owner_id !== undefined) {
|
||||
required.push(AdminACLs.GUILD_TRANSFER_OWNERSHIP);
|
||||
}
|
||||
return required.length > 0 ? required : [AdminACLs.WILDCARD];
|
||||
if (required.length > 0) {
|
||||
return required;
|
||||
}
|
||||
return Object.keys(body).length === 0 ? [] : [AdminACLs.WILDCARD];
|
||||
}
|
||||
|
||||
function requireAllAdminACLs(granted: ReadonlySet<string>, required: ReadonlyArray<string>): void {
|
||||
@@ -148,7 +151,7 @@ export function GuildAdminController(app: HonoApp) {
|
||||
operationId: 'update_admin_guild',
|
||||
summary: 'Update guild',
|
||||
description:
|
||||
'Partially updates a guild. The permissions required are selected by the fields present in the body and are evaluated with all-of semantics: name requires GUILD_UPDATE_NAME, vanity_url_code requires GUILD_UPDATE_VANITY, new_owner_id requires GUILD_TRANSFER_OWNERSHIP, add_features and remove_features require GUILD_UPDATE_FEATURES, and fields together with every other setting requires GUILD_UPDATE_SETTINGS. A body carrying no field requires the wildcard permission. Every applied change is logged to the audit log.',
|
||||
'Partially updates a guild. The permissions required are selected by the fields present in the body and are evaluated with all-of semantics: name requires GUILD_UPDATE_NAME, vanity_url_code requires GUILD_UPDATE_VANITY, new_owner_id requires GUILD_TRANSFER_OWNERSHIP, add_features and remove_features require GUILD_UPDATE_FEATURES, and fields together with every other setting requires GUILD_UPDATE_SETTINGS. A body with no fields applies no change. Every applied change is logged to the audit log.',
|
||||
responseSchema: GuildUpdateResponse,
|
||||
statusCode: 200,
|
||||
security: 'adminApiKey',
|
||||
|
||||
@@ -81,15 +81,17 @@ describe('Admin guild routes', () => {
|
||||
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_ACL')
|
||||
.execute();
|
||||
});
|
||||
test('PATCH /admin/guilds/{guild_id} rejects an empty patch without the wildcard ACL', async () => {
|
||||
test('PATCH /admin/guilds/{guild_id} applies no change for an empty patch', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
await setUserACLs(harness, admin, ['admin:authenticate', 'guild:update:name']);
|
||||
const guild = await createGuild(harness, admin.token, `Empty Patch Guild ${Date.now()}`);
|
||||
await createBuilder(harness, `${admin.token}`)
|
||||
const name = `Empty Patch Guild ${Date.now()}`;
|
||||
const guild = await createGuild(harness, admin.token, name);
|
||||
const result = await createBuilder<AdminGuildUpdate>(harness, `${admin.token}`)
|
||||
.patch(`/admin/guilds/${guild.id}`)
|
||||
.body({})
|
||||
.expect(HTTP_STATUS.FORBIDDEN, 'MISSING_ACL')
|
||||
.expect(HTTP_STATUS.OK)
|
||||
.execute();
|
||||
expect(result.guild.name).toBe(name);
|
||||
});
|
||||
test('guild member add, ban and removal use the member and ban sub-resources', async () => {
|
||||
const admin = await createTestAccount(harness);
|
||||
|
||||
@@ -314,7 +314,7 @@ Fluxer evaluates authorisation in two stages and reads the body between them. Th
|
||||
- `guild:update:vanity` is selected by `vanity_url_code`.
|
||||
- `guild:transfer_ownership` is selected by `new_owner_id`.
|
||||
|
||||
A body with no field at all selects `*`, so an account without the wildcard cannot send an empty patch.
|
||||
A body with no field at all selects nothing, so an empty patch applies no change.
|
||||
|
||||
### Path parameters
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ The effective ACL set of a session or an Admin OAuth2 bearer credential is the s
|
||||
|
||||
Two operations derive their required ACLs from the validated request body.
|
||||
|
||||
[Update guild](/admin-api/guilds/#update-guild) maps each present body field to one ACL and requires every ACL in that set. `name` maps to `guild:update:name`, `vanity_url_code` maps to `guild:update:vanity`, `new_owner_id` maps to `guild:transfer_ownership`, `add_features` and `remove_features` map to `guild:update:features`, and `fields` together with every remaining setting maps to `guild:update:settings`. A body with none of those fields resolves to `*`, so only a wildcard holder is admitted. The route also names those five ACLs as an ordinary any-of requirement, evaluated before the body is read.
|
||||
[Update guild](/admin-api/guilds/#update-guild) maps each present body field to one ACL and requires every ACL in that set. `name` maps to `guild:update:name`, `vanity_url_code` maps to `guild:update:vanity`, `new_owner_id` maps to `guild:transfer_ownership`, `add_features` and `remove_features` map to `guild:update:features`, and `fields` together with every remaining setting maps to `guild:update:settings`. A body with none of those fields resolves to no ACL at all and applies no change. The route also names those five ACLs as an ordinary any-of requirement, evaluated before the body is read.
|
||||
|
||||
[Queue bulk job](/admin-api/bulk-jobs/#queue-bulk-job) maps its `task` discriminator to one ACL and requires that one. `update_user_flags` maps to `bulk:update:user_flags`, `update_suspicious_activity_flags` maps to `bulk:update:suspicious_activity`, `update_guild_features` maps to `bulk:update:guild_features`, `add_guild_members` maps to `bulk:add:guild_members`, and `schedule_user_deletion` maps to `bulk:delete:users`.
|
||||
|
||||
@@ -45,7 +45,7 @@ Fluxer bounds every grant separately. [Set user ACLs](/admin-api/users/#set-user
|
||||
[Set user ACLs](/admin-api/users/#set-user-acls), [Create Admin API key](/admin-api/api-keys/#create-admin-api-key), and [Update Admin API key](/admin-api/api-keys/#update-admin-api-key) each accept at most 111 ACLs and validate every entry against the registry, so a value outside it returns 400 `INVALID_FORM_BODY`.
|
||||
|
||||
:::caution[`*` satisfies every present and future ACL]
|
||||
It admits the wildcard-only shapes such as an empty [Update guild](/admin-api/guilds/#update-guild) body, lifts the per-key owner check, and lifts the escalation bound on every grant its holder makes.
|
||||
It lifts the per-key owner check and lifts the escalation bound on every grant its holder makes.
|
||||
:::
|
||||
|
||||
The [ACL registry](#acl-registry) below lists every value the instance recognises.
|
||||
|
||||
Reference in New Issue
Block a user