Skip to content

Commit

Permalink
Make the final AccessControl test pass, fixing a revocation inconsi…
Browse files Browse the repository at this point in the history
…stency
  • Loading branch information
beverloo committed Jul 21, 2024
1 parent e4b51c8 commit 1e60664
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 4 deletions.
4 changes: 1 addition & 3 deletions app/lib/auth/AccessControl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,7 @@ describe('AccessControl', () => {
.not.toThrow();
});

it.failing('should be able to revoke permissions for specific events or teams', () => {
// FIXME

it('should be able to revoke permissions for specific events or teams', () => {
const accessControl = new AccessControl({
grants: 'test',
revokes: [
Expand Down
6 changes: 5 additions & 1 deletion app/lib/auth/AccessControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,11 @@ export class AccessControl {
#revokes: AccessList;

constructor(grants: AccessControlParams) {
this.#revokes = new AccessList({ grants: grants.revokes })
this.#revokes = new AccessList({
grants: grants.revokes,
requireSpecificScope: true,
});

this.#grants = new AccessList({
expansions: kPermissionGroups,
grants: grants.grants,
Expand Down
31 changes: 31 additions & 0 deletions app/lib/auth/AccessList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,4 +543,35 @@ describe('AccessList', () => {
expect(accessList.query('test3', { team: 'staff' })).not.toBeUndefined();
expect(accessList.query('test3', { event: '2024', team: 'staff' })).not.toBeUndefined();
});

it('should have the ability to require a specific scope', () => {
// This tests a feature that ignores `kAnyEvent` and `kAnyTeam` values in scoped queries,
// which is behaviour that revocations specific to a particular event depend on without
// invalidating results for other events.

const regularAccessList = new AccessList({
grants: {
permission: 'test',
event: '2024',
},
});

expect(regularAccessList.query('test')).not.toBeUndefined();
expect(regularAccessList.query('test', { event: kAnyEvent })).not.toBeUndefined(); // <--
expect(regularAccessList.query('test', { event: '2024' })).not.toBeUndefined();
expect(regularAccessList.query('test', { event: '2025' })).toBeUndefined();

const requiredScopeAccessList = new AccessList({
grants: {
permission: 'test',
event: '2024',
},
requireSpecificScope: true,
});

expect(requiredScopeAccessList.query('test')).not.toBeUndefined();
expect(requiredScopeAccessList.query('test', { event: kAnyEvent })).toBeUndefined(); // <--
expect(requiredScopeAccessList.query('test', { event: '2024' })).not.toBeUndefined();
expect(requiredScopeAccessList.query('test', { event: '2025' })).toBeUndefined();
});
});
17 changes: 17 additions & 0 deletions app/lib/auth/AccessList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ type AccessListParams = {
* Teams that scoped permission queries should be granted for, on top of scoped grants.
*/
teams?: string;

/**
* Whether the `kAnyEvent` and `kAnyTeam` qualifiers on an access check should be ignored for
* explicitly scoped entries. Important to be able to exclude specific scopes as a revoke.
*/
requireSpecificScope?: boolean;
};

/**
Expand Down Expand Up @@ -101,11 +107,15 @@ export type Result = Pick<Access, 'expanded' | 'global'> & {
* be used directly. Accessors may be exposed on there to provide access when necessary.
*/
export class AccessList {
#requireSpecificScope: boolean;

#access: Map<string, Access> = new Map;
#events: Set<string> = new Set;
#teams: Set<string> = new Set;

constructor(params?: AccessListParams) {
this.#requireSpecificScope = !!params?.requireSpecificScope;

if (!!params?.grants) {
const grants = Array.isArray(params.grants) ? params.grants : [ params.grants ];

Expand Down Expand Up @@ -203,6 +213,13 @@ export class AccessList {
for (const accessScope of access.scopes) {
let global: 'global' | undefined = undefined;

if (this.#requireSpecificScope) {
if (scope.event === kAnyEvent && accessScope.event !== kAnyEvent)
continue;
if (scope.team === kAnyTeam && accessScope.team !== kAnyTeam)
continue;
}

if (!!scope.event && scope.event !== kAnyEvent) {
if (accessScope.event !== scope.event && accessScope.event !== kAnyEvent) {
if (!globalEventAccess)
Expand Down

0 comments on commit 1e60664

Please sign in to comment.