Skip to content

Commit c79631e

Browse files
authored
Merge pull request #474 from weaviate/v6-rbac
v6: RBAC
2 parents b60aade + a11c815 commit c79631e

75 files changed

Lines changed: 3612 additions & 37 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ applicationDefaultJvmArgs += listOf(
7272

7373
## Supported APIs
7474

75+
7576
### Tucked Builder
7677

7778
Tucked Builder is an iteration of the Builder pattern that reduces boilerplate and leverages static typing and autocompletion to help API discovery.
@@ -193,6 +194,7 @@ WeaviateClient wcd = WeaviateClient.connectToWeaviateCloud("my-cluster-url.io",
193194
> ```
194195
> WeaviateClient will be automatically closed when execution exits the block.
195196

197+
196198
#### Authentication
197199

198200
Weaviate supports several authentication methods:
@@ -214,6 +216,7 @@ WeaviateClient.connectToCustom(
214216

215217
Follow the [documentation](https://docs.weaviate.io/deploy/configuration/authentication) for a detailed discussion.
216218

219+
217220
### Collection management
218221

219222
```java
@@ -249,6 +252,7 @@ Other methods in `collections` namespace include:
249252
- `list()` to fetch collection configurations for all existing collections
250253
- `deleteAll()` to drop all collections and their data
251254

255+
252256
#### Using a Collection Handle
253257

254258
Once a collection is created, you can obtain another client object that's scoped to that collection, called a _"handle"_.
@@ -274,6 +278,7 @@ Thread.run(() -> popSongs.forEach(song -> songs.data.insert(song)));
274278
275279
For the rest of the document, assume `songs` is handle for the "Songs" collection defined elsewhere.
276280
281+
277282
#### Generic `PropertiesT`
278283
279284
Weaviate client lets you insert object properties in different "shapes". The compile-time type in which the properties must be passed is determined by a generic paramter in CollectionHandle object.
@@ -283,10 +288,12 @@ In practice this means you'll be passing an instance of `Map<String, Object>` to
283288

284289
If you prefer stricter typing, you can leverage our built-in ORM to work with properties as custom Java types. We will return to this in the **ORM** section later. Assume for now that properties are always being passed around as an "untyped" map.
285290

291+
286292
### Ingesting data
287293

288294
Data operations are concentrated behind the `.data` namespace.
289295

296+
290297
#### Insert single object
291298

292299
```java
@@ -401,6 +408,7 @@ songs.query.nearImage("base64-encoded-image");
401408
> [!TIP]
402409
> The first object returned in a NearObject query will _always_ be the search object itself. To filter it out, use the `.excludeSelf()` helper as in the example above.
403410

411+
404412
#### Keyword and Hybrid search
405413

406414
```java
@@ -481,6 +489,7 @@ Where.property("title").like("summer").not();
481489

482490
Passing `null` and and empty `Where[]` to any of the logical operators as well as to the `.where()` method is safe -- the empty operators will simply be ignored.
483491

492+
484493
#### Grouping results
485494

486495
Every query above has an overloaded variant that accepts a group-by clause.
@@ -502,6 +511,7 @@ songs.query.bm25(
502511

503512
The shape of the response object is different too, see [`QueryResponseGrouped`](./src/main/java/io/weaviate/client6/v1/api/collections/query/QueryResponseGrouped.java).
504513

514+
505515
### Pagination
506516

507517
Paginating a Weaviate collection is straighforward and its API should is instantly familiar. `CursorSpliterator` powers 2 patterns for iterating over objects:
@@ -700,6 +710,7 @@ System.out.println(
700710
701711
Some of these features may be added in future releases.
702712
713+
703714
### Collection alias
704715
705716
```java
@@ -710,6 +721,85 @@ client.collections.update("Songs_Alias", "PopSongs");
710721
client.collections.delete("Songs_Alias");
711722
```
712723
724+
### RBAC
725+
726+
#### Roles
727+
728+
The client supports all permission types existing as of `v1.33`.
729+
730+
```java
731+
import io.weaviate.client6.v1.api.rbac.Permission;
732+
733+
client.roles.create(
734+
"ManagerRole",
735+
Permission.collections("Songs", CollectionsPermission.Action.READ, CollectionsPermission.Action.DELETE),
736+
Permission.backups("Albums", BackupsPermission.Action.MANAGE)
737+
);
738+
assert !client.roles.hasPermission("ManagerRole", Permission.collections("Songs", CollectionsPermission.Action.UPDATE));
739+
740+
client.roles.create(
741+
"ArtistRole",
742+
Permission.collections("Songs", CollectionsPermission.Action.CREATE)
743+
);
744+
745+
client.roles.delete("PromoterRole");
746+
```
747+
748+
#### Users
749+
750+
> [!NOTE]
751+
> Not all modifications which can be done to _DB_ users (managed by Weaviate) are equally applicable to _OIDC_ users (managed by an external IdP).
752+
> For this reason their APIs are separated into two distinct namespaces: `users.db` and `users.oidc`.
753+
754+
```java
755+
// DB users must be either defined in the server's environment configuration or created explicitly
756+
if (!client.users.db.exists("ManagerUser")) {
757+
client.users.db.create("ManagerUser");
758+
}
759+
760+
client.users.db.assignRole("ManagerUser", "ManagerRole");
761+
762+
763+
// OIDC users originate from the IdP and do not need to be (and cannot) be created.
764+
client.users.oidc.assignRole("DaveMustaine", "ArtistRole");
765+
client.users.oidc.assignRole("Tarkan", "ArtistRole");
766+
767+
768+
// There's a number of other actions you can take on a DB user:
769+
Optional<DbUser> user = client.users.db.byName("ManagerUser");
770+
assert user.isPresent();
771+
772+
DbUser manager = user.get();
773+
if (!manager.active()) {
774+
client.users.db.activate(manager.id());
775+
}
776+
777+
String newApiKey = client.users.db.rotateKey(manager.id());
778+
client.users.db.deactivate(manager.id());
779+
client.users.db.delete(manager.id());
780+
```
781+
782+
You can get a brief information about the currently authenticated user:
783+
784+
```java
785+
User current = client.users.myUser();
786+
System.out.println(current.userType()); // Prints "DB_USER", "DB_ENV", or "OIDC".
787+
```
788+
789+
#### Groups
790+
791+
RBAC groups are created by assigning roles to a previously-inexisted groups and remove when no roles are longer assigned to a group.
792+
793+
```java
794+
client.groups.assignRoles("./friend-group", "BestFriendRole", "OldFriendRole");
795+
796+
assert client.groups.knownGroupNames().size() == 1; // "./friend-group"
797+
assert client.groups.assignedRoles("./friend-group").size() == 2;
798+
799+
client.groups.assignRoles("./friend-group", "BestFriendRole", "OldFriendRole");
800+
assert client.groups.knownGroupNames().isEmpty();
801+
```
802+
713803
## Useful resources
714804
715805
- [Documentation](https://weaviate.io/developers/weaviate/current/client-libraries/java.html).

src/it/java/io/weaviate/containers/Weaviate.java

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@
1515
import io.weaviate.client6.v1.internal.ObjectBuilder;
1616

1717
public class Weaviate extends WeaviateContainer {
18-
public static final String VERSION = "1.32.3";
18+
public static final String VERSION = "1.33.0";
1919
public static final String DOCKER_IMAGE = "semitechnologies/weaviate";
20+
public static String OIDC_ISSUER = "https://auth.wcs.api.weaviate.io/auth/realms/SeMI";
2021

2122
private volatile SharedClient clientInstance;
2223

23-
public WeaviateClient getClient() {
24-
return getClient(ObjectBuilder.identity());
25-
}
26-
2724
/**
2825
* Create a new instance of WeaviateClient connected to this container if none
2926
* exist. Get an existing client otherwise.
@@ -32,7 +29,7 @@ public WeaviateClient getClient() {
3229
* that you do not need to {@code close} it manually. It will only truly close
3330
* after the parent Testcontainer is stopped.
3431
*/
35-
public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> fn) {
32+
public WeaviateClient getClient() {
3633
if (!isRunning()) {
3734
start();
3835
}
@@ -42,17 +39,8 @@ public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> f
4239

4340
synchronized (this) {
4441
if (clientInstance == null) {
45-
var host = getHost();
46-
var customFn = ObjectBuilder.partial(fn,
47-
conn -> conn
48-
.scheme("http")
49-
.httpHost(host)
50-
.grpcHost(host)
51-
.httpPort(getMappedPort(8080))
52-
.grpcPort(getMappedPort(50051)));
53-
var config = customFn.apply(new Config.Custom()).build();
5442
try {
55-
clientInstance = new SharedClient(config, this);
43+
clientInstance = new SharedClient(Config.of(defaultConfigFn()), this);
5644
} catch (Exception e) {
5745
throw new RuntimeException("create WeaviateClient for Weaviate container", e);
5846
}
@@ -66,19 +54,26 @@ public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> f
6654
* Prefer using {@link #getClient} unless your test needs the initialization
6755
* steps to run, e.g. OIDC authorization grant exchange.
6856
*/
69-
public WeaviateClient getNewClient(Function<Config.Custom, ObjectBuilder<Config>> fn) {
57+
public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> fn) {
7058
if (!isRunning()) {
7159
start();
7260
}
61+
62+
var customFn = ObjectBuilder.partial(fn, defaultConfigFn());
63+
var config = customFn.apply(new Config.Custom()).build();
64+
try {
65+
return new WeaviateClient(config);
66+
} catch (Exception e) {
67+
throw new RuntimeException("create WeaviateClient for Weaviate container", e);
68+
}
69+
}
70+
71+
private Function<Config.Custom, ObjectBuilder<Config>> defaultConfigFn() {
7372
var host = getHost();
74-
var customFn = ObjectBuilder.partial(fn,
75-
conn -> conn
76-
.scheme("http")
77-
.httpHost(host)
78-
.grpcHost(host)
79-
.httpPort(getMappedPort(8080))
80-
.grpcPort(getMappedPort(50051)));
81-
return WeaviateClient.connectToCustom(customFn);
73+
return conn -> conn
74+
.scheme("http")
75+
.httpHost(host).httpPort(getMappedPort(8080))
76+
.grpcHost(host).grpcPort(getMappedPort(50051));
8277
}
8378

8479
public static Weaviate createDefault() {
@@ -92,7 +87,8 @@ public static Weaviate.Builder custom() {
9287
public static class Builder {
9388
private String versionTag;
9489
private Set<String> enableModules = new HashSet<>();
95-
90+
private Set<String> adminUsers = new HashSet<>();
91+
private Set<String> viewerUsers = new HashSet<>();
9692
private Map<String, String> environment = new HashMap<>();
9793

9894
public Builder() {
@@ -137,6 +133,37 @@ public Builder withOffloadS3(String accessKey, String secretKey) {
137133
return this;
138134
}
139135

136+
public Builder withAdminUsers(String... admins) {
137+
adminUsers.addAll(Arrays.asList(admins));
138+
return this;
139+
}
140+
141+
public Builder withViewerUsers(String... viewers) {
142+
viewerUsers.addAll(Arrays.asList(viewers));
143+
return this;
144+
}
145+
146+
/** Enable RBAC authorization for this container. */
147+
public Builder withRbac() {
148+
environment.put("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "false");
149+
environment.put("AUTHENTICATION_APIKEY_ENABLED", "true");
150+
environment.put("AUTHORIZATION_RBAC_ENABLED", "true");
151+
environment.put("AUTHENTICATION_DB_USERS_ENABLED", "true");
152+
return this;
153+
}
154+
155+
/**
156+
* Enable API-Key authentication for this container.
157+
*
158+
* @param apiKeys Allowed API keys.
159+
*/
160+
public Builder withApiKeys(String... apiKeys) {
161+
environment.put("AUTHENTICATION_APIKEY_ENABLED", "true");
162+
environment.put("AUTHENTICATION_APIKEY_ALLOWED_KEYS", String.join(",",
163+
apiKeys));
164+
return this;
165+
}
166+
140167
public Builder enableTelemetry(boolean enable) {
141168
environment.put("DISABLE_TELEMETRY", Boolean.toString(!enable));
142169
return this;
@@ -170,6 +197,20 @@ public Weaviate build() {
170197
c.withEnv("ENABLE_MODULES", String.join(",", enableModules));
171198
}
172199

200+
var apiKeyUsers = new HashSet<String>();
201+
apiKeyUsers.addAll(adminUsers);
202+
apiKeyUsers.addAll(viewerUsers);
203+
204+
if (!adminUsers.isEmpty()) {
205+
environment.put("AUTHORIZATION_ADMIN_USERS", String.join(",", adminUsers));
206+
}
207+
if (!viewerUsers.isEmpty()) {
208+
environment.put("AUTHORIZATION_VIEWER_USERS", String.join(",", viewerUsers));
209+
}
210+
if (!apiKeyUsers.isEmpty()) {
211+
environment.put("AUTHENTICATION_APIKEY_USERS", String.join(",", apiKeyUsers));
212+
}
213+
173214
environment.forEach((name, value) -> c.withEnv(name, value));
174215
c.withCreateContainerCmdModifier(cmd -> cmd.withHostName("weaviate"));
175216
return c;

src/it/java/io/weaviate/integration/OIDCSupportITest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public void test_clientCredentials() throws Exception {
128128

129129
/** Send an HTTP and gRPC requests using a "sync" client. */
130130
private static void pingWeaviate(final Weaviate container, Authentication auth) throws Exception {
131-
try (final var client = container.getNewClient(conn -> conn.authentication(auth))) {
131+
try (final var client = container.getClient(conn -> conn.authentication(auth))) {
132132
// Make an authenticated HTTP call
133133
Assertions.assertThat(client.isLive()).isTrue();
134134

@@ -143,7 +143,7 @@ private static void pingWeaviate(final Weaviate container, Authentication auth)
143143

144144
/** Send an HTTP and gRPC requests using an "async" client. */
145145
private static void pingWeaviateAsync(final Weaviate container, Authentication auth) throws Exception {
146-
try (final var client = container.getNewClient(conn -> conn.authentication(auth))) {
146+
try (final var client = container.getClient(conn -> conn.authentication(auth))) {
147147
try (final var async = client.async()) {
148148
// Make an authenticated HTTP call
149149
Assertions.assertThat(async.isLive().join()).isTrue();

0 commit comments

Comments
 (0)