refactor(permissions): drop hardcoded group permission map for a flat default

This commit is contained in:
Juan Castro
2026-08-11 18:27:17 -04:00
parent edfd67a83c
commit 3eb9d64bb4
4 changed files with 203 additions and 239 deletions
+13 -46
View File
@@ -88,55 +88,22 @@ const implicit_user_app_permissions = [
},
];
// Pre-v2 each entry below carried a `policy: { 'rate-limit': { max, period } }`
// block attached by a `policyPerm('temp.kv')` / `policyPerm('user.es')` helper,
// and a `driverPolicies` table held the actual `{max, period}` values. Nothing
// ever read those policy blocks in v2 — rate limiting is now declared
// directly on each driver via `@Driver({ rateLimit })` / `readonly rateLimit`
// (see KVStoreDriver, AppDriver, SubdomainDriver, NotificationDriver), with
// per-subscription overrides via `bySubscription`. The grants themselves
// stay so the permission scan still emits a path for each group/permission;
// only the dead policy plumbing was removed.
const hardcoded_user_group_permissions = {
system: {
'ca342a5e-b13d-4dee-9048-58b11a57cc55': {
driver: {},
service: {},
feature: {},
'local-terminal:access': {},
},
'b7220104-7905-4985-b996-649fdcdb3c8f': {
driver: {},
service: {},
'service:hello-world:ii:hello-world': {},
'service:puter-kvstore:ii:puter-kvstore': {},
'driver:puter-kvstore': {},
'service:puter-notifications:ii:crud-q': {},
'service:puter-apps:ii:crud-q': {},
'service:puter-subdomains:ii:crud-q': {},
'service:apps:ii:crud-q': {},
'service:es\\Cnotification:ii:crud-q': {},
'service:es\\Capp:ii:crud-q': {},
'service:app:ii:crud-q': {},
'service:es\\Csubdomain:ii:crud-q': {},
},
'78b1b1dd-c959-44d2-b02c-8735671f9997': {
driver: {},
service: {},
'service:hello-world:ii:hello-world': {},
'service:puter-kvstore:ii:puter-kvstore': {},
'driver:puter-kvstore': {},
'service:es\\Cnotification:ii:crud-q': {},
'service:es\\Capp:ii:crud-q': {},
'service:app:ii:crud-q': {},
'service:es\\Csubdomain:ii:crud-q': {},
'service:apps:ii:crud-q': {},
},
},
// Permissions every user actor holds, regardless of group membership.
//
// Roots only: a grant subsumes everything beneath it, so `service` already
// answers `service:puter-kvstore:ii:puter-kvstore`. Listing descendants adds
// entries no check can ever need.
//
// A floor, not a ceiling — anything that depends on *who* the user is belongs
// in the ACL as a `user_to_group_permissions` / `user_to_user_permissions`
// row. Adding a root here grants it to every user, temp accounts included.
const default_user_permissions = {
driver: {},
service: {},
};
module.exports = {
implicit_user_app_permissions,
default_implicit_user_app_permissions,
hardcoded_user_group_permissions,
default_user_permissions,
};
@@ -222,14 +222,14 @@ describe('PermissionService (integration)', () => {
const { actor } = await makeUserActor();
const allowed = await permService.check(
actor,
`service:nope-${uuidv4()}:ii:read`,
`zztest:nope-${uuidv4()}:ii:read`,
);
expect(allowed).toBeFalsy();
});
it('canManagePermission delegates to check on manage:<perm>', async () => {
const { user, actor } = await makeUserActor();
const perm = `service:manage-test-${uuidv4()}:ii:read`;
const perm = `zztest:manage-test-${uuidv4()}:ii:read`;
// Grant manage:<perm> via the flat store.
await server.stores.permission.setFlatUserPerm(
user.id,
@@ -253,7 +253,7 @@ describe('PermissionService (integration)', () => {
permService.grantUserUserPermission(
actor,
`does-not-exist-${uuidv4()}`,
'service:foo:ii:read',
'zztest:foo:ii:read',
),
).rejects.toMatchObject({ statusCode: 404 });
});
@@ -264,7 +264,7 @@ describe('PermissionService (integration)', () => {
permService.grantUserUserPermission(
actor,
user.username,
'service:foo:ii:read',
'zztest:foo:ii:read',
),
).rejects.toMatchObject({ statusCode: 400 });
});
@@ -277,7 +277,7 @@ describe('PermissionService (integration)', () => {
permService.grantUserUserPermission(
issuer,
target.username,
`service:unmanaged-${uuidv4()}:ii:read`,
`zztest:unmanaged-${uuidv4()}:ii:read`,
),
),
).rejects.toMatchObject({ statusCode: 403 });
@@ -286,7 +286,7 @@ describe('PermissionService (integration)', () => {
it('grant persists when issuer holds manage:<permission>', async () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target, actor: targetActor } = await makeUserActor();
const permission = `service:user-user-${uuidv4()}:ii:read`;
const permission = `zztest:user-user-${uuidv4()}:ii:read`;
await server.stores.permission.setFlatUserPerm(
issuer.id,
`manage:${permission}`,
@@ -316,7 +316,7 @@ describe('PermissionService (integration)', () => {
permService.revokeUserUserPermission(
actor,
`does-not-exist-${uuidv4()}`,
'service:foo:ii:read',
'zztest:foo:ii:read',
),
).rejects.toMatchObject({ statusCode: 404 });
});
@@ -329,7 +329,7 @@ describe('PermissionService (integration)', () => {
permService.revokeUserUserPermission(
issuer,
target.username,
`service:unmanaged-${uuidv4()}:ii:read`,
`zztest:unmanaged-${uuidv4()}:ii:read`,
),
),
).rejects.toMatchObject({ statusCode: 403 });
@@ -359,7 +359,7 @@ describe('PermissionService (integration)', () => {
permService.grantUserAppPermission(
actor,
`does-not-exist-${uuidv4()}`,
'service:foo:ii:read',
'zztest:foo:ii:read',
),
),
).rejects.toMatchObject({ statusCode: 404 });
@@ -368,7 +368,7 @@ describe('PermissionService (integration)', () => {
it('persists a user→app grant and is idempotent', async () => {
const { user, actor } = await makeUserActor();
const app = await makeApp(user.id);
const permission = `service:gua-${uuidv4()}:ii:read`;
const permission = `zztest:gua-${uuidv4()}:ii:read`;
await runWithContext({ actor }, () =>
permService.grantUserAppPermission(actor, app.uid, permission),
@@ -389,7 +389,7 @@ describe('PermissionService (integration)', () => {
it('revokeUserAppPermission removes the row', async () => {
const { user, actor } = await makeUserActor();
const app = await makeApp(user.id);
const permission = `service:rua-${uuidv4()}:ii:read`;
const permission = `zztest:rua-${uuidv4()}:ii:read`;
await runWithContext({ actor }, () =>
permService.grantUserAppPermission(actor, app.uid, permission),
);
@@ -417,7 +417,7 @@ describe('PermissionService (integration)', () => {
permService.revokeUserAppPermission(
appActor,
app.uid,
'service:foo:ii:read',
'zztest:foo:ii:read',
),
).rejects.toMatchObject({ statusCode: 403 });
});
@@ -448,8 +448,8 @@ describe('PermissionService (integration)', () => {
const { user, actor } = await makeUserActor();
const app = await makeApp(user.id);
for (const p of [
`service:rua-${uuidv4()}:ii:read`,
`service:rua-${uuidv4()}:ii:write`,
`zztest:rua-${uuidv4()}:ii:read`,
`zztest:rua-${uuidv4()}:ii:write`,
]) {
await runWithContext({ actor }, () =>
permService.grantUserAppPermission(actor, app.uid, p),
@@ -488,7 +488,7 @@ describe('PermissionService (integration)', () => {
permService.grantDevAppPermission(
actor,
`does-not-exist-${uuidv4()}`,
'service:foo:ii:read',
'zztest:foo:ii:read',
),
),
).rejects.toMatchObject({ statusCode: 404 });
@@ -502,7 +502,7 @@ describe('PermissionService (integration)', () => {
permService.grantDevAppPermission(
actor,
app.uid,
`service:unmanaged-${uuidv4()}:ii:read`,
`zztest:unmanaged-${uuidv4()}:ii:read`,
),
),
).rejects.toMatchObject({ statusCode: 403 });
@@ -511,7 +511,7 @@ describe('PermissionService (integration)', () => {
it('grant persists when manage:<perm> is held', async () => {
const { user, actor } = await makeUserActor();
const app = await makeApp(user.id);
const permission = `service:dev-${uuidv4()}:ii:read`;
const permission = `zztest:dev-${uuidv4()}:ii:read`;
await server.stores.permission.setFlatUserPerm(
user.id,
`manage:${permission}`,
@@ -542,7 +542,7 @@ describe('PermissionService (integration)', () => {
permService.revokeDevAppPermission(
appActor,
app.uid,
'service:foo:ii:read',
'zztest:foo:ii:read',
),
).rejects.toMatchObject({ statusCode: 403 });
});
@@ -566,7 +566,7 @@ describe('PermissionService (integration)', () => {
permService.grantUserGroupPermission(
actor,
{ id: 1, uid: 'grp-doesnt-matter' },
`service:unmanaged-${uuidv4()}:ii:read`,
`zztest:unmanaged-${uuidv4()}:ii:read`,
),
),
).rejects.toMatchObject({ statusCode: 403 });
@@ -577,7 +577,7 @@ describe('PermissionService (integration)', () => {
permService.revokeUserGroupPermission(
{ user: undefined } as unknown as Actor,
{ id: 1, uid: 'grp-x' },
'service:foo:ii:read',
'zztest:foo:ii:read',
),
).rejects.toMatchObject({ statusCode: 403 });
});
@@ -587,7 +587,7 @@ describe('PermissionService (integration)', () => {
it('listUserPermissionIssuers returns the issuer who granted the target a perm', async () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target } = await makeUserActor();
const permission = `service:lst-${uuidv4()}:ii:read`;
const permission = `zztest:lst-${uuidv4()}:ii:read`;
await server.stores.permission.setFlatUserPerm(
issuer.id,
`manage:${permission}`,
@@ -666,7 +666,7 @@ describe('PermissionService (integration)', () => {
it('revoke is visible immediately — a cached "granted" reading is not served', async () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target, actor: targetActor } = await makeUserActor();
const permission = `service:revoke-now-${uuidv4()}:ii:read`;
const permission = `zztest:revoke-now-${uuidv4()}:ii:read`;
await grantManage(issuer, permission);
await runWithContext({ actor: issuerActor }, () =>
@@ -721,7 +721,7 @@ describe('PermissionService (integration)', () => {
it('grant is visible immediately — a cached "denied" reading is not served', async () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target, actor: targetActor } = await makeUserActor();
const permission = `service:grant-now-${uuidv4()}:ii:read`;
const permission = `zztest:grant-now-${uuidv4()}:ii:read`;
await grantManage(issuer, permission);
// Prime a "denied" reading into the cache.
@@ -780,7 +780,7 @@ describe('PermissionService (integration)', () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target, actor: targetActor } = await makeUserActor();
const app = await makeApp(target.id);
const permission = `service:app-revoke-now-${uuidv4()}:ii:read`;
const permission = `zztest:app-revoke-now-${uuidv4()}:ii:read`;
await grantManage(issuer, permission);
await runWithContext({ actor: issuerActor }, () =>
@@ -825,7 +825,7 @@ describe('PermissionService (integration)', () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target, actor: targetActor } = await makeUserActor();
const app = await makeApp(target.id);
const permission = `service:app-grant-now-${uuidv4()}:ii:read`;
const permission = `zztest:app-grant-now-${uuidv4()}:ii:read`;
await grantManage(issuer, permission);
// App is allowed to act with the permission, but the user does
@@ -874,7 +874,7 @@ describe('PermissionService (integration)', () => {
it('revokeUserUserPermission deletes the linked SQL row before resolving', async () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target } = await makeUserActor();
const permission = `service:rvk-sync-${uuidv4()}:ii:read`;
const permission = `zztest:rvk-sync-${uuidv4()}:ii:read`;
await grantManage(issuer, permission);
await runWithContext({ actor: issuerActor }, () =>
permService.grantUserUserPermission(
@@ -906,7 +906,7 @@ describe('PermissionService (integration)', () => {
it('revokeUserUserPermission surfaces a failed SQL delete instead of swallowing it', async () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target } = await makeUserActor();
const permission = `service:rvk-fail-${uuidv4()}:ii:read`;
const permission = `zztest:rvk-fail-${uuidv4()}:ii:read`;
await grantManage(issuer, permission);
await runWithContext({ actor: issuerActor }, () =>
permService.grantUserUserPermission(
@@ -946,7 +946,7 @@ describe('PermissionService (integration)', () => {
it('scan-path warms of the flat view carry an expiry (grants are permanent)', async () => {
const { user: issuer, actor: issuerActor } = await makeUserActor();
const { user: target, actor: targetActor } = await makeUserActor();
const permission = `service:warm-ttl-${uuidv4()}:ii:read`;
const permission = `zztest:warm-ttl-${uuidv4()}:ii:read`;
await grantManage(issuer, permission);
// The linked (SQL) path is a delegation chain: it only grants
// if the issuer holds the permission themselves. Give the
@@ -1088,47 +1088,78 @@ describe('PermissionService — scan paths', () => {
{ ownerUserId },
);
describe('group-derived grants', () => {
it('members of the default user group inherit the system driver grants', async () => {
describe('default user permissions and group grants', () => {
it('grants the default permissions to a member of the default user group', async () => {
const { actor } = await makeGroupedUser();
expect(await permService.check(actor, 'driver:puter-kvstore')).toBe(
true,
);
expect(
await permService.check(
actor,
'service:puter-kvstore:ii:puter-kvstore',
),
).toBe(true);
});
it('a user in no group inherits nothing', async () => {
it('grants the default permissions to a user in no group at all', async () => {
// The floor does not depend on membership, which is what repairs
// the account whose best-effort group insert failed at signup —
// previously locked out of every driver call with no recovery.
const { actor } = await makeLooseUser();
expect(await permService.check(actor, 'driver:puter-kvstore')).toBe(
true,
);
expect(
await permService.check(
actor,
'service:puter-kvstore:ii:puter-kvstore',
),
).toBe(true);
});
it('grants the default permissions to a temp user', async () => {
const { row: temp, actor: tempActor } = await makeLooseUser();
await server.stores.group.addUsers(DEFAULT_TEMP_GROUP_UID, [
temp.username,
]);
expect(
await permService.check(tempActor, 'driver:puter-kvstore'),
).toBe(true);
});
it('does not grant a permission outside the default set', async () => {
// Both used to be admin-only entries in the group-keyed map.
// Nothing grants them now.
const { actor } = await makeLooseUser();
expect(await permService.check(actor, 'local-terminal:access')).toBe(
false,
);
expect(
await permService.check(actor, `feature:${uuidv4()}`),
).toBe(false);
});
it('registerSystemGrantForUsers reaches the user group but not the temp group', async () => {
const permission = `feature:users-only-${uuidv4()}`;
const { actor: member } = await makeGroupedUser();
const { row: temp, actor: tempActor } = await makeLooseUser();
await server.stores.group.addUsers(DEFAULT_TEMP_GROUP_UID, [
temp.username,
]);
permService.registerSystemGrantForUsers(permission);
expect(await permService.check(member, permission)).toBe(true);
expect(await permService.check(tempActor, permission)).toBe(false);
});
it('registerSystemGrantForEveryone reaches temp users too', async () => {
const permission = `feature:everyone-${uuidv4()}`;
const { row: temp, actor: tempActor } = await makeLooseUser();
await server.stores.group.addUsers(DEFAULT_TEMP_GROUP_UID, [
temp.username,
]);
permService.registerSystemGrantForEveryone(permission, {
note: 'test',
});
expect(await permService.check(tempActor, permission)).toBe(true);
it('never queries group membership to resolve a user permission', async () => {
// The membership lookup existed only to re-derive the flattened
// constant above, so no scan should reach for it now.
const { actor } = await makeGroupedUser();
const spy = vi.spyOn(server.stores.group, 'listGroupsWithMember');
try {
expect(
await permService.check(actor, 'driver:puter-kvstore', {
noCache: true,
}),
).toBe(true);
expect(
await permService.check(actor, `zztest:${uuidv4()}:read`, {
noCache: true,
}),
).toBe(false);
expect(spy).not.toHaveBeenCalled();
} finally {
spy.mockRestore();
}
});
it('honours a group grant issued by a user, and drops it on revoke', async () => {
@@ -1372,6 +1403,22 @@ describe('PermissionService — scan paths', () => {
).toBe(false);
});
it('a scoped token does not inherit the default user permissions', async () => {
// The floor applies to user actors only. A token must still carry
// its own row, or a scoped token would silently widen to every
// driver the moment its issuer held the `driver` root.
const { actor } = await makeGroupedUser();
expect(await permService.check(actor, 'driver:puter-kvstore')).toBe(
true,
);
expect(
await permService.check(
scopedToken(actor, `tok-${uuidv4()}`),
'driver:puter-kvstore',
),
).toBe(false);
});
it('a scoped token resolves a permission it carries and its issuer holds', async () => {
const { row, actor } = await makeGroupedUser();
const permission = `zztest:tok-${uuidv4()}:ii:read`;
@@ -1731,3 +1778,53 @@ describe('PermissionService — scan paths', () => {
});
});
});
describe('PermissionService — default user permissions vs. group config', () => {
/** A user with no group membership, on an arbitrary server. */
const makeLooseActor = async (srv: PuterServer): Promise<Actor> => {
const username = `pdg${Math.random().toString(36).slice(2, 10)}`;
const u = await srv.stores.user.create({
username,
uuid: uuidv4(),
password: null,
email: `${username}@test.local`,
requires_email_confirmation: false,
});
return {
user: {
id: u.id,
uuid: u.uuid,
username: u.username,
email: u.email ?? null,
},
};
};
it('grants the default permissions with no default group configured', async () => {
// Self-hosted deployments may run without default groups. The floor
// is not group-derived, so it applies regardless — a deployment that
// clears both no longer has every driver call fail closed.
const bare = await setupTestServer({
default_user_group: '',
default_temp_group: '',
} as never);
try {
const perms = bare.services
.permission as unknown as PermissionService;
const actor = await makeLooseActor(bare);
expect(await perms.check(actor, 'driver:puter-kvstore')).toBe(true);
expect(
await perms.check(
actor,
'service:puter-kvstore:ii:puter-kvstore',
),
).toBe(true);
// Still only the roots the floor names.
expect(await perms.check(actor, 'local-terminal:access')).toBe(
false,
);
} finally {
await bare.shutdown();
}
}, 60_000);
});
@@ -41,7 +41,7 @@ import {
// @ts-ignore — hardcoded-permissions.js is plain JS
import {
default_implicit_user_app_permissions,
hardcoded_user_group_permissions,
default_user_permissions,
implicit_user_app_permissions,
} from '../../data/hardcoded-permissions.js';
import { UserRow } from '../../stores/user/UserStore';
@@ -81,16 +81,6 @@ export class PermissionService extends PuterService {
private readonly rewriters: PermissionRewriter[] = [];
private readonly implicators: PermissionImplicator[] = [];
private readonly exploders: PermissionExploder[] = [];
/**
* System-issued grants registered at runtime by other services. Keyed by
* group UID, then by permission string. Merged with the imported
* `hardcoded_user_group_permissions.system` map during the hc-user-group
* scan.
*/
private readonly systemGrantsByGroupUid: Record<
string,
Record<string, unknown>
> = {};
// -- Extension hooks ----------------------------------------------
//
@@ -110,38 +100,6 @@ export class PermissionService extends PuterService {
this.exploders.push(exploder);
}
/**
* Grant a permission (as issued by `system`) to everyone members of both
* the default user group and the default temp group.
*
* Call from an owning service's `onServerStart` (or later).
*/
registerSystemGrantForEveryone(
permission: string,
data: unknown = {},
): void {
const userGroup = this.config.default_user_group;
const tempGroup = this.config.default_temp_group;
if (userGroup) this.#addSystemGrant(userGroup, permission, data);
if (tempGroup) this.#addSystemGrant(tempGroup, permission, data);
}
/**
* Grant a permission (as issued by `system`) to non-temp users only
* members of the default user group, but not the default temp group.
*/
registerSystemGrantForUsers(permission: string, data: unknown = {}): void {
const userGroup = this.config.default_user_group;
if (userGroup) this.#addSystemGrant(userGroup, permission, data);
}
#addSystemGrant(groupUid: string, permission: string, data: unknown): void {
if (!this.systemGrantsByGroupUid[groupUid]) {
this.systemGrantsByGroupUid[groupUid] = {};
}
this.systemGrantsByGroupUid[groupUid][permission] = data;
}
// -- Rewrite / explode (pure-ish helpers) ------------------------
async rewritePermission(permission: string): Promise<string> {
@@ -373,6 +331,38 @@ export class PermissionService extends PuterService {
}
options = exploded.flat();
// -- default user permissions --
// A group-independent floor every user actor holds, resolved in
// memory (see `default_user_permissions`). Derived actors are
// excluded: an app-under-user is gated by its own implicit grant map
// and an access token by its issuer, both of which recurse into a
// scan of the user actor and so still see this floor.
if (!actor.app && !actor.accessToken && actor.user?.id) {
const granted = options.find((option) =>
Object.prototype.hasOwnProperty.call(
default_user_permissions,
option,
),
);
if (granted !== undefined) {
reading.push({
$: 'option',
key: 'default-user-permission',
permission: granted,
source: 'implied',
by: 'default-user-permission',
data: (default_user_permissions as Record<string, unknown>)[
granted
],
holder_username: actor.user.username,
issuer_username: 'system',
});
reading.push({ $: 'time', value: Date.now() - startTs });
await this.#maybeCacheScan(cacheKey, reading);
return reading;
}
}
// -- shortcut implicators --
let shortCircuit = false;
for (const permission of options) {
@@ -409,7 +399,6 @@ export class PermissionService extends PuterService {
this.#scanNonShortcutImplicators(actor, options, reading),
this.#scanAccessToken(actor, options, reading),
this.#scanUserUser(actor, options, reading, workingState),
this.#scanHcUserGroupUser(actor, options, reading),
this.#scanUserGroup(actor, options, reading),
this.#scanUserAppImplied(actor, options, reading),
this.#scanUserApp(actor, options, reading),
@@ -527,96 +516,6 @@ export class PermissionService extends PuterService {
reading.push(...subReadings);
}
/**
* Resolve permissions that a persistent group's members inherit from an
* issuer (typically `system`) via the hardcoded map in
* `hardcoded-permissions.js`, merged with any runtime grants registered
* through `registerSystemGrantForEveryone` /
* `registerSystemGrantForUsers`.
*/
async #scanHcUserGroupUser(
actor: Actor,
options: string[],
reading: ReadingNode[],
): Promise<void> {
if (actor.app || actor.accessToken) return;
if (!actor.user?.id) return;
const memberGroups = await this.stores.group.listGroupsWithMember(
actor.user.id,
);
if (memberGroups.length === 0) return;
const groupByUid: Record<string, { id: number; uid: string }> = {};
for (const g of memberGroups) {
groupByUid[g.uid] = { id: g.id, uid: g.uid };
}
// Compose the effective issuer → group → permission → data map by
// merging the imported hardcoded data with runtime-registered system
// grants. Runtime grants are always attributed to the `system` issuer.
const hcMap = hardcoded_user_group_permissions as Record<
string,
Record<string, Record<string, unknown>>
>;
const hasRuntimeGrants =
Object.keys(this.systemGrantsByGroupUid).length > 0;
const byIssuer: Record<
string,
Record<string, Record<string, unknown>>
> = hasRuntimeGrants
? { ...hcMap, system: { ...(hcMap.system ?? {}) } }
: hcMap;
if (hasRuntimeGrants) {
for (const [gUid, perms] of Object.entries(
this.systemGrantsByGroupUid,
)) {
byIssuer.system[gUid] = {
...(byIssuer.system[gUid] ?? {}),
...perms,
};
}
}
for (const issuerUsername of Object.keys(byIssuer)) {
const issuerUser =
await this.stores.user.getByUsername(issuerUsername);
if (!issuerUser) continue;
const issuerActor = this.#userToActor(issuerUser);
const issuerGroups = byIssuer[issuerUsername];
for (const groupUid of Object.keys(issuerGroups)) {
if (!groupByUid[groupUid]) continue;
const issuerGroupPerms = issuerGroups[groupUid];
for (const permission of options) {
if (
!Object.prototype.hasOwnProperty.call(
issuerGroupPerms,
permission,
)
)
continue;
const issuerReading = await this.scan(
issuerActor,
permission,
);
reading.push({
$: 'path',
via: 'hc-user-group',
has_terminal: readingHasTerminal(issuerReading),
permission,
data: issuerGroupPerms[permission],
holder_username: actor.user.username,
issuer_username: issuerUsername,
reading: issuerReading,
group_id: groupByUid[groupUid].id,
});
}
}
}
}
async #scanUserGroup(
actor: Actor,
options: string[],
+3 -2
View File
@@ -243,8 +243,9 @@ export const createTestUser = async (
requires_email_confirmation: false,
});
// Base driver permissions (kv, notifications, …) are system grants on
// the default user group — membership is what a verified signup gets.
// Driver permissions no longer come from here (see
// `default_user_permissions`), but group-issued ACL grants do, and a
// verified signup is a member of the default user group.
const { default_user_group } = await loadDefaultConfig();
if (default_user_group) {
await server.stores.group.addUsers(default_user_group, [opts.username]);