From 33e93ce8aed8b83e45440f7da421147d05fa0ac2 Mon Sep 17 00:00:00 2001 From: Tommy Smith Date: Mon, 17 Feb 2025 10:06:01 +0000 Subject: [PATCH 1/2] Update roles API to use latest schema and align with recent py changes --- src/openapi/schema.ts | 1 + src/roles/index.ts | 43 ++++++++++++++++------ src/roles/integration.test.ts | 68 ++++++++++++++++++++++++----------- src/roles/types.ts | 10 +++++- src/roles/util.ts | 22 ++++++++++-- 5 files changed, 109 insertions(+), 35 deletions(-) diff --git a/src/openapi/schema.ts b/src/openapi/schema.ts index cc3a08a7..d6f3bbb4 100644 --- a/src/openapi/schema.ts +++ b/src/openapi/schema.ts @@ -349,6 +349,7 @@ export interface definitions { | 'update_collections' | 'delete_collections' | 'assign_and_revoke_users' + | 'read_users' | 'create_tenants' | 'read_tenants' | 'update_tenants' diff --git a/src/roles/index.ts b/src/roles/index.ts index 13436a50..98ec8194 100644 --- a/src/roles/index.ts +++ b/src/roles/index.ts @@ -10,6 +10,7 @@ import { PermissionsInput, Role, RolesPermission, + UsersPermission, } from './types.js'; import { Map } from './util.js'; @@ -166,21 +167,28 @@ export const permissions = { return out; }); }, - nodes: (args: { - collection: string | string[]; - verbosity?: 'verbose' | 'minimal'; - read?: boolean; - }): NodesPermission[] => { - const collections = Array.isArray(args.collection) ? args.collection : [args.collection]; - return collections.flatMap((collection) => { + nodes: { + minimal: (args: { read?: boolean }): NodesPermission[] => { const out: NodesPermission = { - collection, + collection: '*', actions: [], - verbosity: args.verbosity || 'verbose', + verbosity: 'minimal', }; if (args.read) out.actions.push('read_nodes'); - return out; - }); + return [out]; + }, + verbose: (args: { collection: string | string[]; read?: boolean }): NodesPermission[] => { + const collections = Array.isArray(args.collection) ? args.collection : [args.collection]; + return collections.flatMap((collection) => { + const out: NodesPermission = { + collection, + actions: [], + verbosity: 'verbose', + }; + if (args.read) out.actions.push('read_nodes'); + return out; + }); + }, }, roles: (args: { role: string | string[]; @@ -199,6 +207,19 @@ export const permissions = { return out; }); }, + users: (args: { + user: string | string[]; + assign_and_revoke?: boolean; + read?: boolean; + }): UsersPermission[] => { + const users = Array.isArray(args.user) ? args.user : [args.user]; + return users.flatMap((user) => { + const out: UsersPermission = { users: user, actions: [] }; + if (args.assign_and_revoke) out.actions.push('assign_and_revoke_users'); + if (args.read) out.actions.push('read_users'); + return out; + }); + }, }; export default roles; diff --git a/src/roles/integration.test.ts b/src/roles/integration.test.ts index 9a2bec3b..1b346818 100644 --- a/src/roles/integration.test.ts +++ b/src/roles/integration.test.ts @@ -1,9 +1,5 @@ import weaviate, { ApiKey, Permission, Role, WeaviateClient } from '..'; -import { - WeaviateInsufficientPermissionsError, - WeaviateStartUpError, - WeaviateUnexpectedStatusCodeError, -} from '../errors'; +import { WeaviateStartUpError, WeaviateUnexpectedStatusCodeError } from '../errors'; import { DbVersion } from '../utils/dbVersion'; const only = DbVersion.fromString(`v${process.env.WEAVIATE_VERSION!}`).isAtLeast(1, 29, 0) @@ -34,17 +30,6 @@ only('Integration testing of the roles namespace', () => { }) ).rejects.toThrowError(WeaviateStartUpError)); - it('should fail with insufficient permissions if permission-less key provided', async () => { - const unauthenticatedClient = await weaviate.connectToLocal({ - port: 8091, - grpcPort: 50062, - authCredentials: new ApiKey('custom-key'), - }); - await expect(unauthenticatedClient.roles.listAll()).rejects.toThrowError( - WeaviateInsufficientPermissionsError - ); - }); - it('should check the existance of a real role', async () => { const exists = await client.roles.exists('admin'); expect(exists).toBeTruthy(); @@ -73,6 +58,7 @@ only('Integration testing of the roles namespace', () => { dataPermissions: [], nodesPermissions: [], rolesPermissions: [], + usersPermissions: [], }, }, { @@ -86,6 +72,7 @@ only('Integration testing of the roles namespace', () => { dataPermissions: [], nodesPermissions: [], rolesPermissions: [], + usersPermissions: [], }, }, { @@ -110,6 +97,7 @@ only('Integration testing of the roles namespace', () => { dataPermissions: [], nodesPermissions: [], rolesPermissions: [], + usersPermissions: [], }, }, { @@ -134,17 +122,17 @@ only('Integration testing of the roles namespace', () => { ], nodesPermissions: [], rolesPermissions: [], + usersPermissions: [], }, }, { - roleName: 'nodes', - permissions: weaviate.permissions.nodes({ + roleName: 'nodes-verbose', + permissions: weaviate.permissions.nodes.verbose({ collection: 'Some-collection', - verbosity: 'verbose', read: true, }), expected: { - name: 'nodes', + name: 'nodes-verbose', backupsPermissions: [], clusterPermissions: [], collectionsPermissions: [], @@ -153,6 +141,23 @@ only('Integration testing of the roles namespace', () => { { collection: 'Some-collection', verbosity: 'verbose', actions: ['read_nodes'] }, ], rolesPermissions: [], + usersPermissions: [], + }, + }, + { + roleName: 'nodes-minimal', + permissions: weaviate.permissions.nodes.minimal({ + read: true, + }), + expected: { + name: 'nodes-minimal', + backupsPermissions: [], + clusterPermissions: [], + collectionsPermissions: [], + dataPermissions: [], + nodesPermissions: [{ collection: '*', verbosity: 'minimal', actions: ['read_nodes'] }], + rolesPermissions: [], + usersPermissions: [], }, }, { @@ -174,6 +179,25 @@ only('Integration testing of the roles namespace', () => { rolesPermissions: [ { role: 'some-role', actions: ['create_roles', 'read_roles', 'update_roles', 'delete_roles'] }, ], + usersPermissions: [], + }, + }, + { + roleName: 'users', + permissions: weaviate.permissions.users({ + user: 'some-user', + assign_and_revoke: true, + read: true, + }), + expected: { + name: 'users', + backupsPermissions: [], + clusterPermissions: [], + collectionsPermissions: [], + dataPermissions: [], + nodesPermissions: [], + rolesPermissions: [], + usersPermissions: [{ users: 'some-user', actions: ['assign_and_revoke_users', 'read_users'] }], }, }, ]; @@ -194,7 +218,9 @@ only('Integration testing of the roles namespace', () => { afterAll(() => Promise.all( - ['backups', 'cluster', 'collections', 'data', 'nodes', 'roles'].map((n) => client.roles.delete(n)) + ['backups', 'cluster', 'collections', 'data', 'nodes-verbose', 'nodes-minimal', 'roles', 'users'].map( + (n) => client.roles.delete(n) + ) ) ); }); diff --git a/src/roles/types.ts b/src/roles/types.ts index 60cdd172..86af2743 100644 --- a/src/roles/types.ts +++ b/src/roles/types.ts @@ -16,6 +16,7 @@ export type DataAction = Extract< >; export type NodesAction = Extract; export type RolesAction = Extract; +export type UsersAction = Extract; export type BackupsPermission = { collection: string; @@ -47,6 +48,11 @@ export type RolesPermission = { actions: RolesAction[]; }; +export type UsersPermission = { + users: string; + actions: UsersAction[]; +}; + export type Role = { name: string; backupsPermissions: BackupsPermission[]; @@ -55,6 +61,7 @@ export type Role = { dataPermissions: DataPermission[]; nodesPermissions: NodesPermission[]; rolesPermissions: RolesPermission[]; + usersPermissions: UsersPermission[]; }; export type Permission = @@ -63,6 +70,7 @@ export type Permission = | CollectionsPermission | DataPermission | NodesPermission - | RolesPermission; + | RolesPermission + | UsersPermission; export type PermissionsInput = Permission | Permission[] | Permission[][] | (Permission | Permission[])[]; diff --git a/src/roles/util.ts b/src/roles/util.ts index 0915cba4..4452e5a1 100644 --- a/src/roles/util.ts +++ b/src/roles/util.ts @@ -15,6 +15,8 @@ import { Role, RolesAction, RolesPermission, + UsersAction, + UsersPermission, } from './types.js'; export class PermissionGuards { @@ -46,6 +48,8 @@ export class PermissionGuards { PermissionGuards.includes(permission, 'read_nodes'); static isRoles = (permission: Permission): permission is RolesPermission => PermissionGuards.includes(permission, 'create_role', 'read_roles', 'update_roles', 'delete_roles'); + static isUsers = (permission: Permission): permission is UsersPermission => + PermissionGuards.includes(permission, 'read_users', 'assign_and_revoke_users'); static isPermission = (permissions: PermissionsInput): permissions is Permission => !Array.isArray(permissions); static isPermissionArray = (permissions: PermissionsInput): permissions is Permission[] => @@ -89,6 +93,8 @@ export class Map { })); } else if (PermissionGuards.isRoles(permission)) { return Array.from(permission.actions).map((action) => ({ roles: { role: permission.role }, action })); + } else if (PermissionGuards.isUsers(permission)) { + return Array.from(permission.actions).map((action) => ({ users: { users: permission.users }, action })); } else { throw new Error(`Unknown permission type: ${JSON.stringify(permission, null, 2)}`); } @@ -102,6 +108,7 @@ export class Map { data: {} as Record, nodes: {} as Record, roles: {} as Record, + users: {} as Record, }; role.permissions.forEach((permission) => { if (permission.backups !== undefined) { @@ -123,9 +130,14 @@ export class Map { if (perms.data[key] === undefined) perms.data[key] = { collection: key, actions: [] }; perms.data[key].actions.push(permission.action as DataAction); } else if (permission.nodes !== undefined) { - const { collection, verbosity } = permission.nodes; - if (collection === undefined) throw new Error('Nodes permission missing collection'); + let { collection } = permission.nodes; + const { verbosity } = permission.nodes; if (verbosity === undefined) throw new Error('Nodes permission missing verbosity'); + if (verbosity === 'verbose') { + if (collection === undefined) throw new Error('Nodes permission missing collection'); + } else if (verbosity === 'minimal') collection = '*'; + else throw new Error('Nodes permission missing verbosity'); + const key = `${collection}#${verbosity}`; if (perms.nodes[key] === undefined) perms.nodes[key] = { collection, verbosity, actions: [] }; perms.nodes[key].actions.push(permission.action as NodesAction); @@ -134,6 +146,11 @@ export class Map { if (key === undefined) throw new Error('Roles permission missing role'); if (perms.roles[key] === undefined) perms.roles[key] = { role: key, actions: [] }; perms.roles[key].actions.push(permission.action as RolesAction); + } else if (permission.users !== undefined) { + const key = permission.users.users; + if (key === undefined) throw new Error('Users permission missing user'); + if (perms.users[key] === undefined) perms.users[key] = { users: key, actions: [] }; + perms.users[key].actions.push(permission.action as UsersAction); } }); return { @@ -144,6 +161,7 @@ export class Map { dataPermissions: Object.values(perms.data), nodesPermissions: Object.values(perms.nodes), rolesPermissions: Object.values(perms.roles), + usersPermissions: Object.values(perms.users), }; }; From a5b3c497ccf7daa4e0592553d3a12b9c7b6a5edc Mon Sep 17 00:00:00 2001 From: Tommy Smith Date: Mon, 17 Feb 2025 10:12:54 +0000 Subject: [PATCH 2/2] Update CI image --- .github/workflows/main.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index ed689977..e995f4ac 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -12,7 +12,7 @@ env: WEAVIATE_126: 1.26.14 WEAVIATE_127: 1.27.11 WEAVIATE_128: 1.28.4 - WEAVIATE_129: 1.29.0-rc.1 + WEAVIATE_129: 1.29.0-rc.2 jobs: checks: