From 97722822ed63245e3040fa59f268ca06761298a1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 27 Oct 2026 20:46:54 +0100 Subject: [PATCH] Bugfix/owner as grantee of private access (#8321) * Fix owner as grantee of private access * Update changelog --- CHANGELOG.md | 1 + .../endpoints/accesses/access.controller.ts | 11 ++++-- .../endpoints/accesses/accesses.controller.ts | 11 ++++-- libs/common/src/lib/helper.spec.ts | 35 +++++++++++++------ libs/common/src/lib/helper.ts | 11 ++++-- 5 files changed, 52 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42d7fb1710..753a758e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the double submission of forms in the account detail dialog, the create or update access dialog, the historical market data editor, the import activities dialog, the coupon system and the create asset profile dialog of the admin control panel +- Fixed an issue where users could grant a private access to themselves ## 3.93.0 - 2026-10-25 diff --git a/apps/api/src/app/endpoints/accesses/access.controller.ts b/apps/api/src/app/endpoints/accesses/access.controller.ts index 4d3c574762..f244507e7f 100644 --- a/apps/api/src/app/endpoints/accesses/access.controller.ts +++ b/apps/api/src/app/endpoints/accesses/access.controller.ts @@ -102,7 +102,13 @@ export class AccessController { ); } - if (!isValidGranteeOfAccess({ granteeUserId: data.granteeUserId, type })) { + if ( + !isValidGranteeOfAccess({ + type, + granteeUserId: data.granteeUserId, + userId: this.request.user.id + }) + ) { throw new HttpException( getReasonPhrase(StatusCodes.BAD_REQUEST), StatusCodes.BAD_REQUEST @@ -205,7 +211,8 @@ export class AccessController { if ( !isValidGranteeOfAccess({ granteeUserId: data.granteeUserId, - type: originalAccess.type + type: originalAccess.type, + userId: this.request.user.id }) ) { throw new HttpException( diff --git a/apps/api/src/app/endpoints/accesses/accesses.controller.ts b/apps/api/src/app/endpoints/accesses/accesses.controller.ts index 78db7a135b..c6f333dbe5 100644 --- a/apps/api/src/app/endpoints/accesses/accesses.controller.ts +++ b/apps/api/src/app/endpoints/accesses/accesses.controller.ts @@ -75,7 +75,13 @@ export class AccessesController { ); } - if (!isValidGranteeOfAccess({ granteeUserId: data.granteeUserId, type })) { + if ( + !isValidGranteeOfAccess({ + type, + granteeUserId: data.granteeUserId, + userId: this.request.user.id + }) + ) { throw new HttpException( getReasonPhrase(StatusCodes.BAD_REQUEST), StatusCodes.BAD_REQUEST @@ -195,7 +201,8 @@ export class AccessesController { if ( !isValidGranteeOfAccess({ granteeUserId: data.granteeUserId, - type: originalAccess.type + type: originalAccess.type, + userId: this.request.user.id }) ) { throw new HttpException( diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts index b4ef699e5b..7ba92aee9a 100644 --- a/libs/common/src/lib/helper.spec.ts +++ b/libs/common/src/lib/helper.spec.ts @@ -691,41 +691,54 @@ describe('Helper', () => { describe('Is valid grantee of access', () => { const granteeUserId = 'ffb08949-2f8a-4b6e-88fd-0f1e6b6b5f5d'; + const userId = '6b5d4e2a-7c1f-4a3b-9e8d-2f0c1a5b7d3e'; it('A private access with a grantee', () => { expect( - isValidGranteeOfAccess({ granteeUserId, type: 'PRIVATE' }) + isValidGranteeOfAccess({ granteeUserId, userId, type: 'PRIVATE' }) ).toEqual(true); }); it('A private access without a grantee', () => { - expect(isValidGranteeOfAccess({ type: 'PRIVATE' })).toEqual(false); + expect(isValidGranteeOfAccess({ userId, type: 'PRIVATE' })).toEqual( + false + ); }); it('A private access with an empty grantee', () => { expect( - isValidGranteeOfAccess({ granteeUserId: null, type: 'PRIVATE' }) + isValidGranteeOfAccess({ userId, granteeUserId: null, type: 'PRIVATE' }) + ).toEqual(false); + }); + + it('A private access with its owner as grantee', () => { + expect( + isValidGranteeOfAccess({ + userId, + granteeUserId: userId, + type: 'PRIVATE' + }) ).toEqual(false); }); it('A public access without a grantee', () => { - expect(isValidGranteeOfAccess({ type: 'PUBLIC' })).toEqual(true); + expect(isValidGranteeOfAccess({ userId, type: 'PUBLIC' })).toEqual(true); }); it('A public access with a grantee', () => { - expect(isValidGranteeOfAccess({ granteeUserId, type: 'PUBLIC' })).toEqual( - false - ); + expect( + isValidGranteeOfAccess({ granteeUserId, userId, type: 'PUBLIC' }) + ).toEqual(false); }); it('An access of the model context protocol without a grantee', () => { - expect(isValidGranteeOfAccess({ type: 'MCP' })).toEqual(true); + expect(isValidGranteeOfAccess({ userId, type: 'MCP' })).toEqual(true); }); it('An access of the model context protocol with a grantee', () => { - expect(isValidGranteeOfAccess({ granteeUserId, type: 'MCP' })).toEqual( - false - ); + expect( + isValidGranteeOfAccess({ granteeUserId, userId, type: 'MCP' }) + ).toEqual(false); }); }); diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 242cf4ef57..4f01d289f3 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -766,15 +766,22 @@ export function isValidDateAfter1970(aDate: Date | string) { * A private access is granted to a user, while a public access and an access * of a client of the model context protocol are credentials on their own and * have no grantee. A row which mixes both is neither, hence it is rejected. + * The owner of an access cannot be its grantee. */ export function isValidGranteeOfAccess({ granteeUserId, - type + type, + userId }: { granteeUserId?: string | null; type: AccessType; + userId: string; }) { - return type === 'PRIVATE' ? !!granteeUserId : !granteeUserId; + if (type === 'PRIVATE') { + return !!granteeUserId && granteeUserId !== userId; + } + + return !granteeUserId; } export function isValidSearchQuery(aQuery: string) {