Skip to content

Commit

Permalink
Merge pull request #266 from weaviate/1.29/align-roles-api-with-py-cl…
Browse files Browse the repository at this point in the history
…ient

Update roles API to use latest schema and align with recent py changes
  • Loading branch information
tsmith023 authored Feb 17, 2025
2 parents 3f440ad + a5b3c49 commit 5146541
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/openapi/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ export interface definitions {
| 'update_collections'
| 'delete_collections'
| 'assign_and_revoke_users'
| 'read_users'
| 'create_tenants'
| 'read_tenants'
| 'update_tenants'
Expand Down
43 changes: 32 additions & 11 deletions src/roles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PermissionsInput,
Role,
RolesPermission,
UsersPermission,
} from './types.js';
import { Map } from './util.js';

Expand Down Expand Up @@ -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[];
Expand All @@ -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;
68 changes: 47 additions & 21 deletions src/roles/integration.test.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -73,6 +58,7 @@ only('Integration testing of the roles namespace', () => {
dataPermissions: [],
nodesPermissions: [],
rolesPermissions: [],
usersPermissions: [],
},
},
{
Expand All @@ -86,6 +72,7 @@ only('Integration testing of the roles namespace', () => {
dataPermissions: [],
nodesPermissions: [],
rolesPermissions: [],
usersPermissions: [],
},
},
{
Expand All @@ -110,6 +97,7 @@ only('Integration testing of the roles namespace', () => {
dataPermissions: [],
nodesPermissions: [],
rolesPermissions: [],
usersPermissions: [],
},
},
{
Expand All @@ -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: [],
Expand All @@ -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: [],
},
},
{
Expand All @@ -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'] }],
},
},
];
Expand All @@ -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)
)
)
);
});
10 changes: 9 additions & 1 deletion src/roles/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type DataAction = Extract<
>;
export type NodesAction = Extract<Action, 'read_nodes'>;
export type RolesAction = Extract<Action, 'create_roles' | 'read_roles' | 'update_roles' | 'delete_roles'>;
export type UsersAction = Extract<Action, 'read_users' | 'assign_and_revoke_users'>;

export type BackupsPermission = {
collection: string;
Expand Down Expand Up @@ -47,6 +48,11 @@ export type RolesPermission = {
actions: RolesAction[];
};

export type UsersPermission = {
users: string;
actions: UsersAction[];
};

export type Role = {
name: string;
backupsPermissions: BackupsPermission[];
Expand All @@ -55,6 +61,7 @@ export type Role = {
dataPermissions: DataPermission[];
nodesPermissions: NodesPermission[];
rolesPermissions: RolesPermission[];
usersPermissions: UsersPermission[];
};

export type Permission =
Expand All @@ -63,6 +70,7 @@ export type Permission =
| CollectionsPermission
| DataPermission
| NodesPermission
| RolesPermission;
| RolesPermission
| UsersPermission;

export type PermissionsInput = Permission | Permission[] | Permission[][] | (Permission | Permission[])[];
22 changes: 20 additions & 2 deletions src/roles/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
Role,
RolesAction,
RolesPermission,
UsersAction,
UsersPermission,
} from './types.js';

export class PermissionGuards {
Expand Down Expand Up @@ -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[] =>
Expand Down Expand Up @@ -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)}`);
}
Expand All @@ -102,6 +108,7 @@ export class Map {
data: {} as Record<string, DataPermission>,
nodes: {} as Record<string, NodesPermission>,
roles: {} as Record<string, RolesPermission>,
users: {} as Record<string, UsersPermission>,
};
role.permissions.forEach((permission) => {
if (permission.backups !== undefined) {
Expand All @@ -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);
Expand All @@ -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 {
Expand All @@ -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),
};
};

Expand Down

0 comments on commit 5146541

Please sign in to comment.