Skip to content

Commit 1b67dbf

Browse files
authored
Merge pull request #263 from weaviate/1.29/refactor-rbac-for-new-server
Refactor `roles` namespace, adding `users`, to align with new Python API
2 parents c7be3f2 + 5aa2b03 commit 1b67dbf

File tree

10 files changed

+331
-172
lines changed

10 files changed

+331
-172
lines changed

.github/workflows/main.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ env:
1212
WEAVIATE_126: 1.26.14
1313
WEAVIATE_127: 1.27.11
1414
WEAVIATE_128: 1.28.4
15-
WEAVIATE_129: 1.29.0-rc.0
15+
WEAVIATE_129: 1.29.0-rc.0-a8c0bce
1616

1717
jobs:
1818
checks:

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { LiveChecker, OpenidConfigurationGetter, ReadyChecker } from './misc/ind
3939
import weaviateV2 from './v2/index.js';
4040

4141
import { ConsistencyLevel } from './data/replication.js';
42+
import users, { Users } from './users/index.js';
4243

4344
export type ProtocolParams = {
4445
/**
@@ -105,6 +106,7 @@ export interface WeaviateClient {
105106
collections: Collections;
106107
oidcAuth?: OidcAuthenticator;
107108
roles: Roles;
109+
users: Users;
108110

109111
close: () => Promise<void>;
110112
getMeta: () => Promise<Meta>;
@@ -224,6 +226,7 @@ async function client(params: ClientParams): Promise<WeaviateClient> {
224226
cluster: cluster(connection),
225227
collections: collections(connection, dbVersionSupport),
226228
roles: roles(connection),
229+
users: users(connection),
227230
close: () => Promise.resolve(connection.close()), // hedge against future changes to add I/O to .close()
228231
getMeta: () => new MetaGetter(connection).do(),
229232
getOpenIDConfig: () => new OpenidConfigurationGetter(connection.http).do(),

src/openapi/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export type WeaviateMultiTenancyConfig = WeaviateClass['multiTenancyConfig'];
5454
export type WeaviateReplicationConfig = WeaviateClass['replicationConfig'];
5555
export type WeaviateShardingConfig = WeaviateClass['shardingConfig'];
5656
export type WeaviateShardStatus = definitions['ShardStatusGetResponse'];
57+
export type WeaviateUser = definitions['UserInfo'];
5758
export type WeaviateVectorIndexConfig = WeaviateClass['vectorIndexConfig'];
5859
export type WeaviateVectorsConfig = WeaviateClass['vectorConfig'];
5960
export type WeaviateVectorConfig = definitions['VectorConfig'];

src/roles/index.ts

Lines changed: 93 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,86 @@ import {
1010
PermissionsInput,
1111
Role,
1212
RolesPermission,
13-
User,
1413
} from './types.js';
1514
import { Map } from './util.js';
1615

1716
export interface Roles {
17+
/**
18+
* Retrieve all the roles in the system.
19+
*
20+
* @returns {Promise<Record<string, Role>>} A map of role names to their respective roles.
21+
*/
1822
listAll: () => Promise<Record<string, Role>>;
19-
ofCurrentUser: () => Promise<Record<string, Role>>;
23+
/**
24+
* Retrieve a role by its name.
25+
*
26+
* @param {string} roleName The name of the role to retrieve.
27+
* @returns {Promise<Role | null>} The role if it exists, or null if it does not.
28+
*/
2029
byName: (roleName: string) => Promise<Role | null>;
21-
byUser: (user: string) => Promise<Record<string, Role>>;
22-
assignedUsers: (roleName: string) => Promise<Record<string, User>>;
30+
/**
31+
* Retrieve the user IDs assigned to a role.
32+
*
33+
* @param {string} roleName The name of the role to retrieve the assigned user IDs for.
34+
* @returns {Promise<string[]>} The user IDs assigned to the role.
35+
*/
36+
assignedUserIds: (roleName: string) => Promise<string[]>;
37+
/**
38+
* Delete a role by its name.
39+
*
40+
* @param {string} roleName The name of the role to delete.
41+
* @returns {Promise<void>} A promise that resolves when the role is deleted.
42+
*/
2343
delete: (roleName: string) => Promise<void>;
44+
/**
45+
* Create a new role.
46+
*
47+
* @param {string} roleName The name of the new role.
48+
* @param {PermissionsInput} permissions The permissions to assign to the new role.
49+
* @returns {Promise<Role>} The newly created role.
50+
*/
2451
create: (roleName: string, permissions: PermissionsInput) => Promise<Role>;
25-
assignToUser: (roleNames: string | string[], user: string) => Promise<void>;
52+
/**
53+
* Check if a role exists.
54+
*
55+
* @param {string} roleName The name of the role to check for.
56+
* @returns {Promise<boolean>} A promise that resolves to true if the role exists, or false if it does not.
57+
*/
2658
exists: (roleName: string) => Promise<boolean>;
27-
revokeFromUser: (roleNames: string | string[], user: string) => Promise<void>;
59+
/**
60+
* Add permissions to a role.
61+
*
62+
* @param {string} roleName The name of the role to add permissions to.
63+
* @param {PermissionsInput} permissions The permissions to add.
64+
* @returns {Promise<void>} A promise that resolves when the permissions are added.
65+
*/
2866
addPermissions: (roleName: string, permissions: PermissionsInput) => Promise<void>;
67+
/**
68+
* Remove permissions from a role.
69+
*
70+
* @param {string} roleName The name of the role to remove permissions from.
71+
* @param {PermissionsInput} permissions The permissions to remove.
72+
* @returns {Promise<void>} A promise that resolves when the permissions are removed.
73+
*/
2974
removePermissions: (roleName: string, permissions: PermissionsInput) => Promise<void>;
30-
hasPermission: (roleName: string, permission: Permission) => Promise<boolean>;
75+
/**
76+
* Check if a role has the specified permissions.
77+
*
78+
* @param {string} roleName The name of the role to check.
79+
* @param {Permission | Permission[]} permission The permission or permissions to check for.
80+
* @returns {Promise<boolean>} A promise that resolves to true if the role has the permissions, or false if it does not.
81+
*/
82+
hasPermissions: (roleName: string, permission: Permission | Permission[]) => Promise<boolean>;
3183
}
3284

3385
const roles = (connection: ConnectionREST): Roles => {
3486
return {
3587
listAll: () => connection.get<WeaviateRole[]>('/authz/roles').then(Map.roles),
36-
ofCurrentUser: () => connection.get<WeaviateRole[]>('/authz/users/own-roles').then(Map.roles),
3788
byName: (roleName: string) =>
3889
connection.get<WeaviateRole>(`/authz/roles/${roleName}`).then(Map.roleFromWeaviate),
39-
byUser: (user: string) => connection.get<WeaviateRole[]>(`/authz/users/${user}/roles`).then(Map.roles),
40-
assignedUsers: (roleName: string) =>
41-
connection.get<string[]>(`/authz/roles/${roleName}/users`).then(Map.users),
90+
assignedUserIds: (roleName: string) => connection.get<string[]>(`/authz/roles/${roleName}/users`),
4291
create: (roleName: string, permissions: PermissionsInput) => {
43-
const perms = Map.flattenPermissions(permissions).map(Map.permissionToWeaviate);
92+
const perms = Map.flattenPermissions(permissions).flatMap(Map.permissionToWeaviate);
4493
return connection
4594
.postEmpty<WeaviateRole>('/authz/roles', {
4695
name: roleName,
@@ -54,43 +103,34 @@ const roles = (connection: ConnectionREST): Roles => {
54103
.get(`/authz/roles/${roleName}`)
55104
.then(() => true)
56105
.catch(() => false),
57-
assignToUser: (roleNames: string | string[], user: string) =>
58-
connection.postEmpty(`/authz/users/${user}/assign`, {
59-
roles: Array.isArray(roleNames) ? roleNames : [roleNames],
60-
}),
61-
revokeFromUser: (roleNames: string | string[], user: string) =>
62-
connection.postEmpty(`/authz/users/${user}/revoke`, {
63-
roles: Array.isArray(roleNames) ? roleNames : [roleNames],
64-
}),
65106
addPermissions: (roleName: string, permissions: PermissionsInput) =>
66107
connection.postEmpty(`/authz/roles/${roleName}/add-permissions`, { permissions }),
67108
removePermissions: (roleName: string, permissions: PermissionsInput) =>
68109
connection.postEmpty(`/authz/roles/${roleName}/remove-permissions`, { permissions }),
69-
hasPermission: (roleName: string, permission: Permission) =>
70-
connection.postReturn<WeaviatePermission, boolean>(
71-
`/authz/roles/${roleName}/has-permission`,
72-
Map.permissionToWeaviate(permission)
73-
),
110+
hasPermissions: (roleName: string, permission: Permission | Permission[]) =>
111+
Promise.all(
112+
(Array.isArray(permission) ? permission : [permission])
113+
.flatMap((p) => Map.permissionToWeaviate(p))
114+
.map((p) =>
115+
connection.postReturn<WeaviatePermission, boolean>(`/authz/roles/${roleName}/has-permission`, p)
116+
)
117+
).then((r) => r.every((b) => b)),
74118
};
75119
};
76120

77121
export const permissions = {
78122
backup: (args: { collection: string | string[]; manage?: boolean }): BackupsPermission[] => {
79123
const collections = Array.isArray(args.collection) ? args.collection : [args.collection];
80124
return collections.flatMap((collection) => {
81-
const out: BackupsPermission[] = [];
82-
if (args.manage) {
83-
out.push({ collection, action: 'manage_backups' });
84-
}
125+
const out: BackupsPermission = { collection, actions: [] };
126+
if (args.manage) out.actions.push('manage_backups');
85127
return out;
86128
});
87129
},
88130
cluster: (args: { read?: boolean }): ClusterPermission[] => {
89-
const out: ClusterPermission[] = [];
90-
if (args.read) {
91-
out.push({ action: 'read_cluster' });
92-
}
93-
return out;
131+
const out: ClusterPermission = { actions: [] };
132+
if (args.read) out.actions.push('read_cluster');
133+
return [out];
94134
},
95135
collections: (args: {
96136
collection: string | string[];
@@ -101,19 +141,11 @@ export const permissions = {
101141
}): CollectionsPermission[] => {
102142
const collections = Array.isArray(args.collection) ? args.collection : [args.collection];
103143
return collections.flatMap((collection) => {
104-
const out: CollectionsPermission[] = [];
105-
if (args.create_collection) {
106-
out.push({ collection, action: 'create_collections' });
107-
}
108-
if (args.read_config) {
109-
out.push({ collection, action: 'read_collections' });
110-
}
111-
if (args.update_config) {
112-
out.push({ collection, action: 'update_collections' });
113-
}
114-
if (args.delete_collection) {
115-
out.push({ collection, action: 'delete_collections' });
116-
}
144+
const out: CollectionsPermission = { collection, actions: [] };
145+
if (args.create_collection) out.actions.push('create_collections');
146+
if (args.read_config) out.actions.push('read_collections');
147+
if (args.update_config) out.actions.push('update_collections');
148+
if (args.delete_collection) out.actions.push('delete_collections');
117149
return out;
118150
});
119151
},
@@ -126,19 +158,11 @@ export const permissions = {
126158
}): DataPermission[] => {
127159
const collections = Array.isArray(args.collection) ? args.collection : [args.collection];
128160
return collections.flatMap((collection) => {
129-
const out: DataPermission[] = [];
130-
if (args.create) {
131-
out.push({ collection, action: 'create_data' });
132-
}
133-
if (args.read) {
134-
out.push({ collection, action: 'read_data' });
135-
}
136-
if (args.update) {
137-
out.push({ collection, action: 'update_data' });
138-
}
139-
if (args.delete) {
140-
out.push({ collection, action: 'delete_data' });
141-
}
161+
const out: DataPermission = { collection, actions: [] };
162+
if (args.create) out.actions.push('create_data');
163+
if (args.read) out.actions.push('read_data');
164+
if (args.update) out.actions.push('update_data');
165+
if (args.delete) out.actions.push('delete_data');
142166
return out;
143167
});
144168
},
@@ -149,23 +173,21 @@ export const permissions = {
149173
}): NodesPermission[] => {
150174
const collections = Array.isArray(args.collection) ? args.collection : [args.collection];
151175
return collections.flatMap((collection) => {
152-
const out: NodesPermission[] = [];
153-
if (args.read) {
154-
out.push({ collection, action: 'read_nodes', verbosity: args.verbosity || 'verbose' });
155-
}
176+
const out: NodesPermission = {
177+
collection,
178+
actions: [],
179+
verbosity: args.verbosity || 'verbose',
180+
};
181+
if (args.read) out.actions.push('read_nodes');
156182
return out;
157183
});
158184
},
159185
roles: (args: { role: string | string[]; read?: boolean; manage?: boolean }): RolesPermission[] => {
160186
const roles = Array.isArray(args.role) ? args.role : [args.role];
161187
return roles.flatMap((role) => {
162-
const out: RolesPermission[] = [];
163-
if (args.read) {
164-
out.push({ role, action: 'read_roles' });
165-
}
166-
if (args.manage) {
167-
out.push({ role, action: 'manage_roles' });
168-
}
188+
const out: RolesPermission = { role, actions: [] };
189+
if (args.read) out.actions.push('read_roles');
190+
if (args.manage) out.actions.push('manage_roles');
169191
return out;
170192
});
171193
},

src/roles/integration.test.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from '../errors';
77
import { DbVersion } from '../utils/dbVersion';
88

9-
const only = DbVersion.fromString(`v${process.env.WEAVIATE_VERSION!}`).isAtLeast(1, 28, 0)
9+
const only = DbVersion.fromString(`v${process.env.WEAVIATE_VERSION!}`).isAtLeast(1, 29, 0)
1010
? describe
1111
: describe.skip;
1212

@@ -45,11 +45,6 @@ only('Integration testing of the roles namespace', () => {
4545
);
4646
});
4747

48-
it('should get roles by user', async () => {
49-
const roles = await client.roles.byUser('admin-user');
50-
expect(Object.keys(roles).length).toBeGreaterThan(0);
51-
});
52-
5348
it('should check the existance of a real role', async () => {
5449
const exists = await client.roles.exists('admin');
5550
expect(exists).toBeTruthy();
@@ -72,7 +67,7 @@ only('Integration testing of the roles namespace', () => {
7267
permissions: weaviate.permissions.backup({ collection: 'Some-collection', manage: true }),
7368
expected: {
7469
name: 'backups',
75-
backupsPermissions: [{ collection: 'Some-collection', action: 'manage_backups' }],
70+
backupsPermissions: [{ collection: 'Some-collection', actions: ['manage_backups'] }],
7671
clusterPermissions: [],
7772
collectionsPermissions: [],
7873
dataPermissions: [],
@@ -86,7 +81,7 @@ only('Integration testing of the roles namespace', () => {
8681
expected: {
8782
name: 'cluster',
8883
backupsPermissions: [],
89-
clusterPermissions: [{ action: 'read_cluster' }],
84+
clusterPermissions: [{ actions: ['read_cluster'] }],
9085
collectionsPermissions: [],
9186
dataPermissions: [],
9287
nodesPermissions: [],
@@ -107,10 +102,10 @@ only('Integration testing of the roles namespace', () => {
107102
backupsPermissions: [],
108103
clusterPermissions: [],
109104
collectionsPermissions: [
110-
{ collection: 'Some-collection', action: 'create_collections' },
111-
{ collection: 'Some-collection', action: 'read_collections' },
112-
{ collection: 'Some-collection', action: 'update_collections' },
113-
{ collection: 'Some-collection', action: 'delete_collections' },
105+
{
106+
collection: 'Some-collection',
107+
actions: ['create_collections', 'read_collections', 'update_collections', 'delete_collections'],
108+
},
114109
],
115110
dataPermissions: [],
116111
nodesPermissions: [],
@@ -132,10 +127,10 @@ only('Integration testing of the roles namespace', () => {
132127
clusterPermissions: [],
133128
collectionsPermissions: [],
134129
dataPermissions: [
135-
{ collection: 'Some-collection', action: 'create_data' },
136-
{ collection: 'Some-collection', action: 'read_data' },
137-
{ collection: 'Some-collection', action: 'update_data' },
138-
{ collection: 'Some-collection', action: 'delete_data' },
130+
{
131+
collection: 'Some-collection',
132+
actions: ['create_data', 'read_data', 'update_data', 'delete_data'],
133+
},
139134
],
140135
nodesPermissions: [],
141136
rolesPermissions: [],
@@ -154,7 +149,9 @@ only('Integration testing of the roles namespace', () => {
154149
clusterPermissions: [],
155150
collectionsPermissions: [],
156151
dataPermissions: [],
157-
nodesPermissions: [{ collection: 'Some-collection', verbosity: 'verbose', action: 'read_nodes' }],
152+
nodesPermissions: [
153+
{ collection: 'Some-collection', verbosity: 'verbose', actions: ['read_nodes'] },
154+
],
158155
rolesPermissions: [],
159156
},
160157
},
@@ -168,7 +165,7 @@ only('Integration testing of the roles namespace', () => {
168165
collectionsPermissions: [],
169166
dataPermissions: [],
170167
nodesPermissions: [],
171-
rolesPermissions: [{ role: 'some-role', action: 'manage_roles' }],
168+
rolesPermissions: [{ role: 'some-role', actions: ['manage_roles'] }],
172169
},
173170
},
174171
];
@@ -186,4 +183,10 @@ only('Integration testing of the roles namespace', () => {
186183
await expect(client.roles.byName('backups')).rejects.toThrowError(WeaviateUnexpectedStatusCodeError);
187184
await expect(client.roles.exists('backups')).resolves.toBeFalsy();
188185
});
186+
187+
afterAll(() =>
188+
Promise.all(
189+
['backups', 'cluster', 'collections', 'data', 'nodes', 'roles'].map((n) => client.roles.delete(n))
190+
)
191+
);
189192
});

0 commit comments

Comments
 (0)