sendRequest(String endpoint, Object payload, String method,
HttpResponse response = this.sendHttpRequest(endpoint, payload, method);
int statusCode = response.getStatusCode();
String responseBody = response.getBody();
-
if (statusCode < 399) {
T body = toResponse(responseBody, classOfT);
return new Response<>(statusCode, body, null);
diff --git a/src/main/java/io/weaviate/client/v1/backup/api/BackupCanceler.java b/src/main/java/io/weaviate/client/v1/backup/api/BackupCanceler.java
index 15d8890a5..ac557566a 100644
--- a/src/main/java/io/weaviate/client/v1/backup/api/BackupCanceler.java
+++ b/src/main/java/io/weaviate/client/v1/backup/api/BackupCanceler.java
@@ -15,7 +15,8 @@
* BackupCanceler can cancel an in-progress backup by ID.
*
*
- * Canceling backups which have successfully completed before being interrupted is not supported and will result in an error.
+ * Canceling backups which have successfully completed before being interrupted
+ * is not supported and will result in an error.
*/
public class BackupCanceler extends BaseClient implements ClientResult {
private String backend;
@@ -70,4 +71,3 @@ private String path() {
return path;
}
}
-
diff --git a/src/main/java/io/weaviate/client/v1/rbac/Roles.java b/src/main/java/io/weaviate/client/v1/rbac/Roles.java
new file mode 100644
index 000000000..c1c0c63e7
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/Roles.java
@@ -0,0 +1,93 @@
+package io.weaviate.client.v1.rbac;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.api.AssignedUsersGetter;
+import io.weaviate.client.v1.rbac.api.PermissionAdder;
+import io.weaviate.client.v1.rbac.api.PermissionChecker;
+import io.weaviate.client.v1.rbac.api.PermissionRemover;
+import io.weaviate.client.v1.rbac.api.RoleAllGetter;
+import io.weaviate.client.v1.rbac.api.RoleAssigner;
+import io.weaviate.client.v1.rbac.api.RoleCreator;
+import io.weaviate.client.v1.rbac.api.RoleDeleter;
+import io.weaviate.client.v1.rbac.api.RoleExists;
+import io.weaviate.client.v1.rbac.api.RoleGetter;
+import io.weaviate.client.v1.rbac.api.RoleRevoker;
+import io.weaviate.client.v1.rbac.api.UserRolesGetter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class Roles {
+
+ private final HttpClient httpClient;
+ private final Config config;
+
+ /** Create a new role. */
+ public RoleCreator creator() {
+ return new RoleCreator(httpClient, config);
+ }
+
+ /** Delete a role. */
+ public RoleDeleter deleter() {
+ return new RoleDeleter(httpClient, config);
+ }
+
+ /**
+ * Add permissions to an existing role.
+ * Note: This method is an upsert operation. If the permission already exists,
+ * it will be updated. If it does not exist, it will be created.
+ */
+ public PermissionAdder permissionAdder() {
+ return new PermissionAdder(httpClient, config);
+ }
+
+ /**
+ * Remove permissions from a role.
+ * Note: This method is a downsert operation. If the permission does not
+ * exist, it will be ignored. If these permissions are the only permissions of
+ * the role, the role will be deleted.
+ */
+ public PermissionRemover permissionRemover() {
+ return new PermissionRemover(httpClient, config);
+ }
+
+ /** Check if a role has a permission. */
+ public PermissionChecker permissionChecker() {
+ return new PermissionChecker(httpClient, config);
+ }
+
+ /** Get all existing roles. */
+ public RoleAllGetter allGetter() {
+ return new RoleAllGetter(httpClient, config);
+ };
+
+ /** Get role and its associated permissions. */
+ public RoleGetter getter() {
+ return new RoleGetter(httpClient, config);
+ };
+
+ /** Get roles assigned to a user. */
+ public UserRolesGetter userRolesGetter() {
+ return new UserRolesGetter(httpClient, config);
+ };
+
+ /** Get users assigned to a role. */
+ public AssignedUsersGetter assignedUsersGetter() {
+ return new AssignedUsersGetter(httpClient, config);
+ };
+
+ /** Check if a role exists. */
+ public RoleExists exists() {
+ return new RoleExists(httpClient, config);
+ }
+
+ /** Assign a role to a user. Note that 'root' cannot be assigned. */
+ public RoleAssigner assigner() {
+ return new RoleAssigner(httpClient, config);
+ }
+
+ /** Revoke a role from a user. Note that 'root' cannot be revoked. */
+ public RoleRevoker revoker() {
+ return new RoleRevoker(httpClient, config);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/AssignedUsersGetter.java b/src/main/java/io/weaviate/client/v1/rbac/api/AssignedUsersGetter.java
new file mode 100644
index 000000000..263d170c4
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/AssignedUsersGetter.java
@@ -0,0 +1,39 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Response;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+
+public class AssignedUsersGetter extends BaseClient implements ClientResult> {
+ private String role;
+
+ public AssignedUsersGetter(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public AssignedUsersGetter withRole(String role) {
+ this.role = role;
+ return this;
+ }
+
+ @Override
+ public Result> run() {
+ Response resp = sendGetRequest(path(), String[].class);
+ List roles = Optional.ofNullable(resp.getBody())
+ .map(Arrays::asList)
+ .orElse(new ArrayList<>());
+ return new Result<>(resp.getStatusCode(), roles, resp.getErrors());
+ }
+
+ private String path() {
+ return String.format("/authz/roles/%s/users", this.role);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java
new file mode 100644
index 000000000..e98c5b65f
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionAdder.java
@@ -0,0 +1,47 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.model.Permission;
+import lombok.AllArgsConstructor;
+
+public class PermissionAdder extends BaseClient implements ClientResult {
+ private String role;
+ private List> permissions = new ArrayList<>();
+
+ public PermissionAdder(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public PermissionAdder withRole(String name) {
+ this.role = name;
+ return this;
+ }
+
+ public PermissionAdder withPermissions(Permission>... permissions) {
+ this.permissions = Arrays.asList(permissions);
+ return this;
+ }
+
+ @AllArgsConstructor
+ private static class Body {
+ public final List> permissions;
+ }
+
+ @Override
+ public Result run() {
+ List permissions = WeaviatePermission.mergePermissions(this.permissions);
+ return new Result(sendPostRequest(path(), new Body(permissions), Void.class));
+ }
+
+ private String path() {
+ return String.format("/authz/roles/%s/add-permissions", this.role);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionChecker.java b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionChecker.java
new file mode 100644
index 000000000..330e1e96b
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionChecker.java
@@ -0,0 +1,36 @@
+package io.weaviate.client.v1.rbac.api;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.model.Permission;
+
+public class PermissionChecker extends BaseClient implements ClientResult {
+ private String role;
+ private Permission> permission;
+
+ public PermissionChecker(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public PermissionChecker withRole(String role) {
+ this.role = role;
+ return this;
+ }
+
+ public PermissionChecker withPermission(Permission> permission) {
+ this.permission = permission;
+ return this;
+ }
+
+ @Override
+ public Result run() {
+ return new Result(sendPostRequest(path(), permission.toWeaviate(), Boolean.class));
+ }
+
+ private String path() {
+ return String.format("/authz/roles/%s/has-permission", this.role);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java
new file mode 100644
index 000000000..2d76eeab3
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/PermissionRemover.java
@@ -0,0 +1,47 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.model.Permission;
+import lombok.AllArgsConstructor;
+
+public class PermissionRemover extends BaseClient implements ClientResult {
+ private String role;
+ private List> permissions = new ArrayList<>();
+
+ public PermissionRemover(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public PermissionRemover withRole(String role) {
+ this.role = role;
+ return this;
+ }
+
+ public PermissionRemover withPermissions(Permission>... permissions) {
+ this.permissions = Arrays.asList(permissions);
+ return this;
+ }
+
+ @AllArgsConstructor
+ private static class Body {
+ public final List> permissions;
+ }
+
+ @Override
+ public Result run() {
+ List permissions = WeaviatePermission.mergePermissions(this.permissions);
+ return new Result(sendPostRequest(path(), new Body(permissions), Void.class));
+ }
+
+ private String path() {
+ return String.format("/authz/roles/%s/remove-permissions", this.role);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/RoleAllGetter.java b/src/main/java/io/weaviate/client/v1/rbac/api/RoleAllGetter.java
new file mode 100644
index 000000000..fe1f55bdc
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleAllGetter.java
@@ -0,0 +1,33 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Response;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.model.Role;
+
+public class RoleAllGetter extends BaseClient implements ClientResult> {
+
+ public RoleAllGetter(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ @Override
+ public Result> run() {
+ Response resp = sendGetRequest("/authz/roles", WeaviateRole[].class);
+ List roles = Optional.ofNullable(resp.getBody())
+ .map(Arrays::asList)
+ .orElse(new ArrayList<>())
+ .stream()
+ .map(w -> w.toRole())
+ .toList();
+ return new Result<>(resp.getStatusCode(), roles, resp.getErrors());
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/RoleAssigner.java b/src/main/java/io/weaviate/client/v1/rbac/api/RoleAssigner.java
new file mode 100644
index 000000000..cc9ca3686
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleAssigner.java
@@ -0,0 +1,46 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import lombok.AllArgsConstructor;
+
+public class RoleAssigner extends BaseClient implements ClientResult {
+ private String user;
+ private List roles = new ArrayList<>();
+
+ public RoleAssigner(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public RoleAssigner withUser(String user) {
+ this.user = user;
+ return this;
+ }
+
+ public RoleAssigner witRoles(String... roles) {
+ this.roles = Arrays.asList(roles);
+ return this;
+ }
+
+ /** The API signature for this method is { "roles": [...] } */
+ @AllArgsConstructor
+ private static class Body {
+ public final List roles;
+ }
+
+ @Override
+ public Result run() {
+ return new Result(sendPostRequest(path(), new Body(this.roles), Void.class));
+ }
+
+ private String path() {
+ return String.format("/authz/users/%s/assign", this.user);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java b/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java
new file mode 100644
index 000000000..66078f9b5
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleCreator.java
@@ -0,0 +1,37 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.model.Permission;
+
+public class RoleCreator extends BaseClient implements ClientResult {
+ private String name;
+ private List> permissions = new ArrayList<>();
+
+ public RoleCreator(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public RoleCreator withName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public RoleCreator withPermissions(Permission>... permissions) {
+ this.permissions = Arrays.asList(permissions);
+ return this;
+ }
+
+ @Override
+ public Result run() {
+ WeaviateRole role = new WeaviateRole(this.name, this.permissions);
+ return new Result(sendPostRequest("/authz/roles", role, Void.class));
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/RoleDeleter.java b/src/main/java/io/weaviate/client/v1/rbac/api/RoleDeleter.java
new file mode 100644
index 000000000..4a25b8847
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleDeleter.java
@@ -0,0 +1,25 @@
+package io.weaviate.client.v1.rbac.api;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+
+public class RoleDeleter extends BaseClient implements ClientResult {
+ private String name;
+
+ public RoleDeleter(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public RoleDeleter withName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Result run() {
+ return new Result(sendDeleteRequest("/authz/roles/" + this.name, null, Void.class));
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/RoleExists.java b/src/main/java/io/weaviate/client/v1/rbac/api/RoleExists.java
new file mode 100644
index 000000000..6b1d5731d
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleExists.java
@@ -0,0 +1,38 @@
+package io.weaviate.client.v1.rbac.api;
+
+import org.apache.hc.core5.http.HttpStatus;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.WeaviateError;
+import io.weaviate.client.base.WeaviateErrorResponse;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.model.Role;
+
+public class RoleExists extends BaseClient implements ClientResult {
+ private RoleGetter getter;
+
+ public RoleExists(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ this.getter = new RoleGetter(httpClient, config);
+ }
+
+ public RoleExists withName(String name) {
+ this.getter.withName(name);
+ return this;
+ }
+
+ @Override
+ public Result run() {
+ Result resp = this.getter.run();
+ if (resp.hasErrors()) {
+ WeaviateError error = resp.getError();
+ return new Result<>(error.getStatusCode(), null,
+ WeaviateErrorResponse.builder().error(error.getMessages()).build());
+
+ }
+ return new Result(HttpStatus.SC_OK, resp.getResult() != null, null);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/RoleGetter.java b/src/main/java/io/weaviate/client/v1/rbac/api/RoleGetter.java
new file mode 100644
index 000000000..c07672311
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleGetter.java
@@ -0,0 +1,31 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.Optional;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Response;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.model.Role;
+
+public class RoleGetter extends BaseClient implements ClientResult {
+ private String name;
+
+ public RoleGetter(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public RoleGetter withName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public Result run() {
+ Response resp = sendGetRequest("/authz/roles/" + this.name, WeaviateRole.class);
+ Role role = Optional.ofNullable(resp.getBody()).map(WeaviateRole::toRole).orElse(null);
+ return new Result(resp.getStatusCode(), role, resp.getErrors());
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java b/src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java
new file mode 100644
index 000000000..d6f888c46
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/RoleRevoker.java
@@ -0,0 +1,47 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import lombok.AllArgsConstructor;
+
+public class RoleRevoker extends BaseClient implements ClientResult {
+ private String user;
+ private List roles = new ArrayList<>();
+
+ public RoleRevoker(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ public RoleRevoker withUser(String user) {
+ this.user = user;
+ return this;
+ }
+
+ public RoleRevoker witRoles(String... roles) {
+ this.roles = Collections.unmodifiableList(Arrays.asList(roles));
+ return this;
+ }
+
+ /** The API signature for this method is { "roles": [...] } */
+ @AllArgsConstructor
+ private static class Body {
+ public final List roles;
+ }
+
+ @Override
+ public Result run() {
+ return new Result(sendPostRequest(path(), new Body(this.roles), Void.class));
+ }
+
+ private String path() {
+ return String.format("/authz/users/%s/revoke", this.user);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java b/src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java
new file mode 100644
index 000000000..5ee7fa2c6
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/UserRolesGetter.java
@@ -0,0 +1,49 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.base.BaseClient;
+import io.weaviate.client.base.ClientResult;
+import io.weaviate.client.base.Response;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.base.http.HttpClient;
+import io.weaviate.client.v1.rbac.model.Role;
+
+public class UserRolesGetter extends BaseClient implements ClientResult> {
+ private String user;
+
+ public UserRolesGetter(HttpClient httpClient, Config config) {
+ super(httpClient, config);
+ }
+
+ /** Leave unset to fetch roles assigned to the current user. */
+ public UserRolesGetter withUser(String user) {
+ this.user = user;
+ return this;
+ }
+
+ @Override
+ public Result> run() {
+ Response resp;
+ if (this.user == null) {
+ resp = sendGetRequest("/authz/users/own-roles", WeaviateRole[].class);
+ } else {
+ resp = sendGetRequest(path(), WeaviateRole[].class);
+ }
+ List roles = Optional.ofNullable(resp.getBody())
+ .map(Arrays::asList)
+ .orElse(new ArrayList<>())
+ .stream()
+ .map(w -> w.toRole())
+ .toList();
+ return new Result<>(resp.getStatusCode(), roles, resp.getErrors());
+ }
+
+ private String path() {
+ return String.format("/authz/users/%s/roles", this.user);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java
new file mode 100644
index 000000000..26d6b159e
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviatePermission.java
@@ -0,0 +1,52 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.List;
+
+import io.weaviate.client.v1.rbac.model.BackupsPermission;
+import io.weaviate.client.v1.rbac.model.CollectionsPermission;
+import io.weaviate.client.v1.rbac.model.DataPermission;
+import io.weaviate.client.v1.rbac.model.NodesPermission;
+import io.weaviate.client.v1.rbac.model.Permission;
+import io.weaviate.client.v1.rbac.model.RolesPermission;
+import io.weaviate.client.v1.rbac.model.TenantsPermission;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+@Getter
+@Builder
+@AllArgsConstructor
+public class WeaviatePermission {
+ String action;
+ BackupsPermission backups;
+ CollectionsPermission collections;
+ DataPermission data;
+ NodesPermission nodes;
+ RolesPermission roles;
+ TenantsPermission tenants;
+
+ public WeaviatePermission(String action) {
+ this.action = action;
+ }
+
+ public > WeaviatePermission(String action, Permission
perm) {
+ this.action = action;
+ if (perm instanceof BackupsPermission) {
+ this.backups = (BackupsPermission) perm;
+ } else if (perm instanceof CollectionsPermission) {
+ this.collections = (CollectionsPermission) perm;
+ } else if (perm instanceof DataPermission) {
+ this.data = (DataPermission) perm;
+ } else if (perm instanceof NodesPermission) {
+ this.nodes = (NodesPermission) perm;
+ } else if (perm instanceof RolesPermission) {
+ this.roles = (RolesPermission) perm;
+ } else if (perm instanceof TenantsPermission) {
+ this.tenants = (TenantsPermission) perm;
+ }
+ }
+
+ public static List mergePermissions(List> permissions) {
+ return permissions.stream().map(perm -> perm.toWeaviate()).toList();
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java
new file mode 100644
index 000000000..2675ac724
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/api/WeaviateRole.java
@@ -0,0 +1,24 @@
+package io.weaviate.client.v1.rbac.api;
+
+import java.util.List;
+
+import io.weaviate.client.v1.rbac.model.Permission;
+import io.weaviate.client.v1.rbac.model.Role;
+import lombok.Getter;
+
+@Getter
+public class WeaviateRole {
+ String name;
+ List permissions;
+
+ public WeaviateRole(String name, List> permissions) {
+ this.name = name;
+ this.permissions = WeaviatePermission.mergePermissions(permissions);
+ }
+
+ public Role toRole() {
+ List> permissions = this.permissions.stream()
+ .>map(perm -> Permission.fromWeaviate(perm)).toList();
+ return new Role(this.name, permissions);
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java
new file mode 100644
index 000000000..e40bafb5e
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/BackupsPermission.java
@@ -0,0 +1,34 @@
+package io.weaviate.client.v1.rbac.model;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class BackupsPermission extends Permission {
+ final String collection;
+
+ public BackupsPermission(Action action, String collection) {
+ super(action);
+ this.collection = collection;
+ }
+
+ BackupsPermission(String action, String collection) {
+ this(CustomAction.fromString(Action.class, action), collection);
+ }
+
+ @Override
+ public WeaviatePermission toWeaviate() {
+ return new WeaviatePermission(this.action, this);
+ }
+
+ @AllArgsConstructor
+ public enum Action implements CustomAction {
+ MANAGE("manage_backups");
+
+ @Getter
+ private final String value;
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java
new file mode 100644
index 000000000..c70dd7a5f
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/ClusterPermission.java
@@ -0,0 +1,31 @@
+package io.weaviate.client.v1.rbac.model;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class ClusterPermission extends Permission {
+ public ClusterPermission(Action action) {
+ super(action);
+ }
+
+ ClusterPermission(String action) {
+ this(CustomAction.fromString(Action.class, action));
+ }
+
+ @Override
+ public WeaviatePermission toWeaviate() {
+ return new WeaviatePermission(this.action);
+ }
+
+ @AllArgsConstructor
+ public enum Action implements CustomAction {
+ READ("read_cluster");
+
+ @Getter
+ private final String value;
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java
new file mode 100644
index 000000000..a5b97e8b1
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/CollectionsPermission.java
@@ -0,0 +1,46 @@
+package io.weaviate.client.v1.rbac.model;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class CollectionsPermission extends Permission {
+ final String collection;
+ final String tenant;
+
+ public CollectionsPermission(Action action, String collection) {
+ this(action, collection, "*");
+ }
+
+ CollectionsPermission(String action, String collection) {
+ this(CustomAction.fromString(Action.class, action), collection);
+ }
+
+ private CollectionsPermission(Action action, String collection, String tenant) {
+ super(action);
+ this.collection = collection;
+ this.tenant = tenant;
+ }
+
+ @Override
+ public WeaviatePermission toWeaviate() {
+ return new WeaviatePermission(this.action, this);
+ }
+
+ @AllArgsConstructor
+ public enum Action implements CustomAction {
+ CREATE("create_collections"),
+ READ("read_collections"),
+ UPDATE("update_collections"),
+ DELETE("delete_collections");
+
+ // Not part of the public API yet.
+ // MANAGE("manage_collections");
+
+ @Getter
+ private final String value;
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java
new file mode 100644
index 000000000..700119b39
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/DataPermission.java
@@ -0,0 +1,46 @@
+package io.weaviate.client.v1.rbac.model;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class DataPermission extends Permission {
+ final String collection;
+ final String object;
+ final String tenant;
+
+ public DataPermission(Action action, String collection) {
+ this(action, collection, "*", "*");
+ }
+
+ DataPermission(String action, String collection) {
+ this(CustomAction.fromString(Action.class, action), collection);
+ }
+
+ private DataPermission(Action action, String collection, String object, String tenant) {
+ super(action);
+ this.collection = collection;
+ this.object = object;
+ this.tenant = tenant;
+ }
+
+ @Override
+ public WeaviatePermission toWeaviate() {
+ return new WeaviatePermission(this.action, this);
+ }
+
+ @AllArgsConstructor
+ public enum Action implements CustomAction {
+ CREATE("create_data"),
+ READ("read_data"),
+ UPDATE("update_data"),
+ DELETE("delete_data"),
+ MANAGE("manage_data");
+
+ @Getter
+ private final String value;
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java
new file mode 100644
index 000000000..2d2794f7d
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/NodesPermission.java
@@ -0,0 +1,55 @@
+package io.weaviate.client.v1.rbac.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class NodesPermission extends Permission {
+ final String collection;
+ final Verbosity verbosity;
+
+ public NodesPermission(Action action, Verbosity verbosity) {
+ this(action, verbosity, "*");
+ }
+
+ NodesPermission(String action, Verbosity verbosity) {
+ this(CustomAction.fromString(Action.class, action), verbosity);
+ }
+
+ NodesPermission(String action, Verbosity verbosity, String collection) {
+ this(CustomAction.fromString(Action.class, action), verbosity, collection);
+ }
+
+ public NodesPermission(Action action, Verbosity verbosity, String collection) {
+ super(action);
+ this.collection = collection;
+ this.verbosity = verbosity;
+ }
+
+ @Override
+ public WeaviatePermission toWeaviate() {
+ return new WeaviatePermission(this.action, this);
+ }
+
+ @AllArgsConstructor
+ public enum Action implements CustomAction {
+ READ("read_nodes");
+
+ @Getter
+ private final String value;
+ }
+
+ @AllArgsConstructor
+ public enum Verbosity {
+ @SerializedName("minimal")
+ MINIMAL,
+ @SerializedName("verbose")
+ VERBOSE;
+ }
+
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java
new file mode 100644
index 000000000..76b1616f3
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/Permission.java
@@ -0,0 +1,106 @@
+package io.weaviate.client.v1.rbac.model;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@EqualsAndHashCode
+public abstract class Permission> {
+ @Getter
+ final transient String action;
+
+ Permission(CustomAction action) {
+ this.action = action.getValue();
+ }
+
+ public abstract WeaviatePermission toWeaviate();
+
+ public static Permission> fromWeaviate(WeaviatePermission perm) {
+ String action = perm.getAction();
+ if (perm.getBackups() != null) {
+ return new BackupsPermission(action, perm.getBackups().getCollection());
+ } else if (perm.getCollections() != null) {
+ return new CollectionsPermission(action, perm.getCollections().getCollection());
+ } else if (perm.getData() != null) {
+ return new DataPermission(action, perm.getData().getCollection());
+ } else if (perm.getNodes() != null) {
+ NodesPermission nodes = perm.getNodes();
+ if (nodes.getCollection() != null) {
+ return new NodesPermission(action, perm.getNodes().getVerbosity(), nodes.getCollection());
+ }
+ return new NodesPermission(action, perm.getNodes().getVerbosity());
+ } else if (perm.getRoles() != null) {
+ return new RolesPermission(action, perm.getRoles().getRole());
+ } else if (perm.getTenants() != null) {
+ return new TenantsPermission(action);
+ } else if (CustomAction.isValid(ClusterPermission.Action.class, action)) {
+ return new ClusterPermission(action);
+ } else if (CustomAction.isValid(UsersPermission.Action.class, action)) {
+ return new UsersPermission(action);
+ }
+ return null;
+ }
+
+ public static BackupsPermission backups(BackupsPermission.Action action, String collection) {
+ return new BackupsPermission(action, collection);
+ }
+
+ public static ClusterPermission cluster(ClusterPermission.Action action) {
+ return new ClusterPermission(action);
+ }
+
+ public static CollectionsPermission collections(CollectionsPermission.Action action, String collection) {
+ return new CollectionsPermission(action, collection);
+ }
+
+ public static DataPermission data(DataPermission.Action action, String collection) {
+ return new DataPermission(action, collection);
+ }
+
+ public static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity) {
+ return new NodesPermission(action, verbosity);
+ }
+
+ public static NodesPermission nodes(NodesPermission.Action action, NodesPermission.Verbosity verbosity,
+ String collection) {
+ return new NodesPermission(action, verbosity, collection);
+ }
+
+ public static RolesPermission roles(RolesPermission.Action action, String role) {
+ return new RolesPermission(action, role);
+ }
+
+ public static TenantsPermission tenants(TenantsPermission.Action action) {
+ return new TenantsPermission(action);
+ }
+
+ // public static UsersPermission users(UsersPermission.Action action) {
+ // return new UsersPermission(action);
+ // }
+
+ public String toString() {
+ return String.format("Permission", this.action);
+ }
+}
+
+interface CustomAction {
+ String getValue();
+
+ static & CustomAction> E fromString(Class enumClass, String value) {
+ for (E action : enumClass.getEnumConstants()) {
+ if (action.getValue().equals(value)) {
+ return action;
+ }
+ }
+ throw new IllegalArgumentException("No enum constant for value: " + value);
+ }
+
+ static boolean isValid(Class enumClass, String value) {
+ for (CustomAction action : enumClass.getEnumConstants()) {
+ if (action.getValue().equals(value)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/Role.java b/src/main/java/io/weaviate/client/v1/rbac/model/Role.java
new file mode 100644
index 000000000..21597d0fc
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/Role.java
@@ -0,0 +1,24 @@
+package io.weaviate.client.v1.rbac.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+@EqualsAndHashCode
+public class Role {
+ public final String name;
+ public List extends Permission>> permissions = new ArrayList<>();
+
+ public String toString() {
+ return String.format(
+ "Role",
+ this.name, permissions.isEmpty()
+ ? "none"
+ : String.join(", ", permissions.stream().map(Permission::getAction).toList()));
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java
new file mode 100644
index 000000000..950d4cca4
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/RolesPermission.java
@@ -0,0 +1,35 @@
+package io.weaviate.client.v1.rbac.model;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class RolesPermission extends Permission {
+ final String role;
+
+ public RolesPermission(Action action, String role) {
+ super(action);
+ this.role = role;
+ }
+
+ RolesPermission(String action, String role) {
+ this(CustomAction.fromString(Action.class, action), role);
+ }
+
+ @Override
+ public WeaviatePermission toWeaviate() {
+ return new WeaviatePermission(this.action, this);
+ }
+
+ @AllArgsConstructor
+ public enum Action implements CustomAction {
+ READ("read_roles"),
+ MANAGE("manage_roles");
+
+ @Getter
+ private final String value;
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java
new file mode 100644
index 000000000..c5002249a
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/TenantsPermission.java
@@ -0,0 +1,41 @@
+package io.weaviate.client.v1.rbac.model;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class TenantsPermission extends Permission {
+ final String tenant;
+
+ public TenantsPermission(Action action) {
+ this(action, "*");
+ }
+
+ TenantsPermission(String action) {
+ this(CustomAction.fromString(Action.class, action));
+ }
+
+ private TenantsPermission(Action action, String tenant) {
+ super(action);
+ this.tenant = tenant;
+ }
+
+ @Override
+ public WeaviatePermission toWeaviate() {
+ return new WeaviatePermission(this.action, this);
+ }
+
+ @AllArgsConstructor
+ public enum Action implements CustomAction {
+ CREATE("create_tenants"),
+ READ("read_tenants"),
+ UPDATE("update_tenants"),
+ DELETE("delete_tenants");
+
+ @Getter
+ private final String value;
+ }
+}
diff --git a/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java
new file mode 100644
index 000000000..b7758bf80
--- /dev/null
+++ b/src/main/java/io/weaviate/client/v1/rbac/model/UsersPermission.java
@@ -0,0 +1,34 @@
+
+package io.weaviate.client.v1.rbac.model;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * UsersPermission controls access to dynamic user management capabilities.
+ * These will be introduced in v1.30. Until then the class will remain
+ * package-private.
+ */
+class UsersPermission extends Permission {
+ public UsersPermission(Action action) {
+ super(action);
+ }
+
+ UsersPermission(String action) {
+ this(CustomAction.fromString(Action.class, action));
+ }
+
+ @Override
+ public WeaviatePermission toWeaviate() {
+ return new WeaviatePermission(this.action);
+ }
+
+ @AllArgsConstructor
+ public enum Action implements CustomAction {
+ MANAGE("manage_users");
+
+ @Getter
+ private final String value;
+ }
+}
diff --git a/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java
new file mode 100644
index 000000000..d3278befb
--- /dev/null
+++ b/src/test/java/io/weaviate/client/v1/rbac/model/PermissionTest.java
@@ -0,0 +1,126 @@
+package io.weaviate.client.v1.rbac.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.function.Supplier;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.testcontainers.shaded.org.hamcrest.Matcher;
+import org.testcontainers.shaded.org.hamcrest.MatcherAssert;
+import org.testcontainers.shaded.org.hamcrest.beans.SamePropertyValuesAs;
+
+import com.jparams.junit4.JParamsTestRunner;
+import com.jparams.junit4.data.DataMethod;
+
+import io.weaviate.client.v1.rbac.api.WeaviatePermission;
+import io.weaviate.client.v1.rbac.model.NodesPermission.Verbosity;
+
+@RunWith(JParamsTestRunner.class)
+public class PermissionTest {
+ public static Object[][] serializationTestCases() {
+ UsersPermission users = new UsersPermission(UsersPermission.Action.MANAGE);
+ BackupsPermission backups = new BackupsPermission(BackupsPermission.Action.MANAGE, "Pizza");
+ DataPermission data = new DataPermission(DataPermission.Action.MANAGE, "Pizza");
+ NodesPermission nodes = new NodesPermission(NodesPermission.Action.READ, Verbosity.MINIMAL, "Pizza");
+ RolesPermission roles = new RolesPermission(RolesPermission.Action.MANAGE, "TestWriter");
+ CollectionsPermission collections = new CollectionsPermission(CollectionsPermission.Action.CREATE, "Pizza");
+ ClusterPermission cluster = new ClusterPermission(ClusterPermission.Action.READ);
+ TenantsPermission tenants = new TenantsPermission(TenantsPermission.Action.READ);
+
+ return new Object[][] {
+ {
+ "user permission",
+ (Supplier>) () -> users,
+ new WeaviatePermission("manage_users"),
+ },
+ {
+ "backup permission",
+ (Supplier>) () -> backups,
+ new WeaviatePermission("manage_backups", backups),
+ },
+ {
+ "data permission",
+ (Supplier>) () -> data,
+ new WeaviatePermission("manage_data", data),
+ },
+ {
+ "nodes permission",
+ (Supplier>) () -> nodes,
+ new WeaviatePermission("read_nodes", nodes),
+ },
+ {
+ "roles permission",
+ (Supplier>) () -> roles,
+ new WeaviatePermission("manage_roles", roles),
+ },
+ {
+ "collections permission",
+ (Supplier>) () -> collections,
+ new WeaviatePermission("create_collections", collections),
+ },
+ {
+ "cluster permission",
+ (Supplier>) () -> cluster,
+ new WeaviatePermission("read_cluster"),
+ },
+ {
+ "tenants permission",
+ (Supplier>) () -> tenants,
+ new WeaviatePermission("read_tenants", tenants),
+ },
+ };
+ }
+
+ @DataMethod(source = PermissionTest.class, method = "serializationTestCases")
+ @Test
+ public void testToWeaviate(String name, Supplier> permFunc, WeaviatePermission expected)
+ throws Exception {
+ Permission> perm = permFunc.get();
+ MatcherAssert.assertThat(name, perm.toWeaviate(), sameAs(expected));
+ }
+
+ private static Matcher sameAs(T expected) {
+ return new SamePropertyValuesAs(expected, new ArrayList<>());
+ }
+
+ @Test
+ public void testDefaultDataPermission() {
+ DataPermission perm = new DataPermission(DataPermission.Action.MANAGE, "Pizza");
+ assertThat(perm).as("data permission must have object=* and tenant=*")
+ .returns("*", DataPermission::getObject)
+ .returns("*", DataPermission::getTenant);
+ }
+
+ @Test
+ public void testDefaultCollectionsPermission() {
+ CollectionsPermission perm = new CollectionsPermission(CollectionsPermission.Action.CREATE, "Pizza");
+ assertThat(perm).as("collection permission must have tenant=*")
+ .returns("*", CollectionsPermission::getTenant);
+ }
+
+ @Test
+ public void testDefaultNodesPermission() {
+ NodesPermission perm = new NodesPermission(NodesPermission.Action.READ, NodesPermission.Verbosity.MINIMAL);
+ assertThat(perm).as("nodes permission should affect all collections if one is not specified")
+ .returns("*", NodesPermission::getCollection);
+ }
+
+ @Test
+ public void testDefaultTenantsPermission() {
+ TenantsPermission perm = new TenantsPermission(TenantsPermission.Action.READ);
+ assertThat(perm).as("tenants permission must have tenant=*")
+ .returns("*", TenantsPermission::getTenant);
+ }
+
+ @DataMethod(source = PermissionTest.class, method = "serializationTestCases")
+ @Test
+ public void testFromWeaviate(String name,
+ Supplier> expectedFunc, WeaviatePermission input)
+ throws Exception {
+ Permission> expected = expectedFunc.get();
+ Permission> actual = Permission.fromWeaviate(input);
+ MatcherAssert.assertThat(name, actual, sameAs(expected));
+ }
+}
diff --git a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java
index bc71c2625..78926d58a 100644
--- a/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java
+++ b/src/test/java/io/weaviate/integration/client/WeaviateDockerCompose.java
@@ -2,6 +2,7 @@
import java.util.ArrayList;
import java.util.List;
+
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
@@ -13,22 +14,40 @@
public class WeaviateDockerCompose implements TestRule {
+ /** Weaviate Docker image to create a container from. */
private final String weaviateVersion;
private final boolean withOffloadS3;
+ /** Username of the admin user for instances using RBAC. */
+ private final String adminUser;
+
public WeaviateDockerCompose() {
this.weaviateVersion = WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE;
this.withOffloadS3 = false;
+ this.adminUser = null;
}
public WeaviateDockerCompose(String version) {
this.weaviateVersion = String.format("semitechnologies/weaviate:%s", version);
this.withOffloadS3 = false;
+ this.adminUser = null;
}
public WeaviateDockerCompose(String version, boolean withOffloadS3) {
this.weaviateVersion = String.format("semitechnologies/weaviate:%s", version);
this.withOffloadS3 = withOffloadS3;
+ this.adminUser = null;
+ }
+
+ public WeaviateDockerCompose(String version, String adminUser) {
+ this.weaviateVersion = WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE;
+ this.withOffloadS3 = false;
+ this.adminUser = adminUser;
+ }
+
+ /** Create docker-compose deployment with auth and RBAC-authz enabled. */
+ public static WeaviateDockerCompose rbac(String adminUser) {
+ return new WeaviateDockerCompose(WeaviateDockerImage.WEAVIATE_DOCKER_IMAGE, adminUser);
}
public static class Weaviate extends WeaviateContainer {
@@ -57,6 +76,27 @@ public Weaviate(String dockerImageName, boolean withOffloadS3) {
withEnv("ENABLE_MODULES", String.join(",", enableModules));
withCreateContainerCmdModifier(cmd -> cmd.withHostName("weaviate"));
}
+
+ /** Create Weaviate container with RBAC authz and an admin user. */
+ public Weaviate(String dockerImageName, boolean withOffloadS3, String adminUser) {
+ this(dockerImageName, withOffloadS3);
+ withEnv("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "false");
+ withEnv("AUTHENTICATION_APIKEY_ENABLED", "true");
+ withEnv("AUTHORIZATION_RBAC_ENABLED", "true");
+ withEnv("AUTHENTICATION_APIKEY_USERS", adminUser);
+ withEnv("AUTHENTICATION_APIKEY_ALLOWED_KEYS", makeSecret(adminUser));
+ withEnv("AUTHORIZATION_ADMIN_USERS", adminUser);
+ }
+
+ /**
+ * Generate API secret for a username. When running an instance with
+ * authentication enabled, {@link Weaviate} will use this method to generate
+ * secrets for all users.
+ * Use this method to get a valid API key for a test client.
+ */
+ public static String makeSecret(String user) {
+ return user + "-secret";
+ }
}
public static class Contextionary extends GenericContainer {
@@ -98,7 +138,11 @@ public void start() {
}
contextionary = new Contextionary();
contextionary.start();
- weaviate = new Weaviate(this.weaviateVersion, withOffloadS3);
+ if (adminUser == null) {
+ weaviate = new Weaviate(this.weaviateVersion, this.withOffloadS3);
+ } else {
+ weaviate = new Weaviate(this.weaviateVersion, this.withOffloadS3, this.adminUser);
+ }
weaviate.start();
}
diff --git a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java
index dc37e52f5..75ef83870 100644
--- a/src/test/java/io/weaviate/integration/client/WeaviateVersion.java
+++ b/src/test/java/io/weaviate/integration/client/WeaviateVersion.java
@@ -3,12 +3,12 @@
public class WeaviateVersion {
// docker image version
- public static final String WEAVIATE_IMAGE = "1.27.7-8f0e033";
+ public static final String WEAVIATE_IMAGE = "1.28.3-f73fcee";
// to be set according to weaviate docker image
- public static final String EXPECTED_WEAVIATE_VERSION = "1.27.7";
+ public static final String EXPECTED_WEAVIATE_VERSION = "1.28.3";
// to be set according to weaviate docker image
- public static final String EXPECTED_WEAVIATE_GIT_HASH = "8f0e033";
+ public static final String EXPECTED_WEAVIATE_GIT_HASH = "f73fcee";
private WeaviateVersion() {
}
diff --git a/src/test/java/io/weaviate/integration/client/async/schema/ClientSchemaTest.java b/src/test/java/io/weaviate/integration/client/async/schema/ClientSchemaTest.java
index 2c15d028e..6d2218548 100644
--- a/src/test/java/io/weaviate/integration/client/async/schema/ClientSchemaTest.java
+++ b/src/test/java/io/weaviate/integration/client/async/schema/ClientSchemaTest.java
@@ -468,7 +468,7 @@ public void testSchemaCreateClassWithInvalidTokenizationProperty() throws Execut
//then
assertResultTrue(createStatus);
- assertResultError("tokenization in body should be one of [word lowercase whitespace field trigram gse kagome_kr]", notExistingTokenizationCreateStatus);
+ assertResultError("tokenization in body should be one of [word lowercase whitespace field trigram gse kagome_kr kagome_ja]", notExistingTokenizationCreateStatus);
assertResultError("Tokenization is not allowed for data type 'int'", notSupportedTokenizationForIntCreateStatus);
}
}
diff --git a/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java
new file mode 100644
index 000000000..9ae2e8999
--- /dev/null
+++ b/src/test/java/io/weaviate/integration/client/rbac/ClientRbacTest.java
@@ -0,0 +1,249 @@
+package io.weaviate.integration.client.rbac;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assumptions.assumeFalse;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
+import org.junit.rules.TestName;
+
+import io.weaviate.client.Config;
+import io.weaviate.client.WeaviateAuthClient;
+import io.weaviate.client.base.Result;
+import io.weaviate.client.v1.auth.exception.AuthException;
+import io.weaviate.client.v1.rbac.Roles;
+import io.weaviate.client.v1.rbac.model.BackupsPermission;
+import io.weaviate.client.v1.rbac.model.ClusterPermission;
+import io.weaviate.client.v1.rbac.model.CollectionsPermission;
+import io.weaviate.client.v1.rbac.model.DataPermission;
+import io.weaviate.client.v1.rbac.model.NodesPermission;
+import io.weaviate.client.v1.rbac.model.NodesPermission.Verbosity;
+import io.weaviate.client.v1.rbac.model.Permission;
+import io.weaviate.client.v1.rbac.model.Role;
+import io.weaviate.client.v1.rbac.model.RolesPermission;
+import io.weaviate.client.v1.rbac.model.TenantsPermission;
+import io.weaviate.integration.client.WeaviateDockerCompose;
+import io.weaviate.integration.client.WeaviateDockerCompose.Weaviate;
+
+public class ClientRbacTest {
+ private static final String adminRole = "admin";
+ private static final String viewerRole = "viewer";
+ private static final String adminUser = "john-doe";
+
+ private Roles roles;
+
+ @Rule
+ public TestName currentTest = new TestName();
+
+ @ClassRule
+ public static WeaviateDockerCompose compose = WeaviateDockerCompose.rbac(adminUser);
+
+ @Before
+ public void before() throws AuthException {
+ Config config = new Config("http", compose.getHttpHostAddress());
+ roles = WeaviateAuthClient.apiKey(config, Weaviate.makeSecret(adminUser)).roles();
+ }
+
+ public static Object[][] rolesToCreate() {
+ return new Object[][] {
+ };
+ }
+
+ /**
+ * By default the admin user which we use to run the tests
+ * will have 'admin' and 'viewer' roles.
+ */
+ @Test
+ public void testGetAll() {
+ Result> response = roles.allGetter().run();
+ List all = response.getResult();
+
+ assertThat(response.getError()).as("get all roles error").isNull();
+ assertThat(all).hasSize(2).as("wrong number of roles");
+ assertThat(all.get(0)).returns(adminRole, Role::getName);
+ assertThat(all.get(1)).returns(viewerRole, Role::getName);
+ }
+
+ @Test
+ public void testGetUserRoles() {
+ Result> responseCurrent = roles.userRolesGetter().run();
+ assertThat(responseCurrent.getError()).as("get roles for current user error").isNull();
+ Result> responseAdminUser = roles.userRolesGetter().withUser(adminUser).run();
+ assertThat(responseAdminUser.getError()).as("get roles for user error").isNull();
+
+ List currentRoles = responseCurrent.getResult();
+ List adminRoles = responseAdminUser.getResult();
+
+ Assertions.assertArrayEquals(currentRoles.toArray(), adminRoles.toArray(), "expect same set of roles");
+ }
+
+ public void testGetAssignedUsers() {
+ Result> response = roles.assignedUsersGetter().withRole(adminRole).run();
+ assertThat(response.getError()).as("get assigned users error").isNull();
+
+ List users = response.getResult();
+ assertThat(users).as("users assigned to " + adminRole + " role").hasSize(1);
+ assertEquals(adminUser, users.get(0), "wrong user assinged to " + adminRole + " role");
+ }
+
+ // TODO: check if I can create a role with a name that's not a valid URL
+ // paramter
+
+ @Test
+ public void testCreate() {
+ String myRole = roleName("VectorOwner");
+ String myCollection = "Pizza";
+
+ Permission>[] wantPermissions = new Permission>[] {
+ Permission.backups(BackupsPermission.Action.MANAGE, myCollection),
+ Permission.cluster(ClusterPermission.Action.READ),
+ Permission.nodes(NodesPermission.Action.READ, Verbosity.MINIMAL, myCollection),
+ Permission.roles(RolesPermission.Action.MANAGE, viewerRole),
+ Permission.collections(CollectionsPermission.Action.CREATE, myCollection),
+ Permission.data(DataPermission.Action.UPDATE, myCollection),
+ Permission.tenants(TenantsPermission.Action.DELETE),
+ };
+
+ try {
+ // Arrange
+ deleteRole(myRole);
+
+ // Act
+ createRole(myRole, wantPermissions);
+
+ Result response = roles.getter().withName(myRole).run();
+ Role role = response.getResult();
+ assertNull("error fetching a role", response.getError());
+ assertThat(role).as("wrong role name").returns(myRole, Role::getName);
+
+ for (int i = 0; i < wantPermissions.length; i++) {
+ Permission> perm = wantPermissions[i];
+ assertTrue("should have permission " + perm, checkHasPermission(myRole, perm));
+ }
+ } finally {
+ deleteRole(myRole);
+ }
+ }
+
+ @Test
+ public void testAddPermissions() {
+ String myRole = roleName("VectorOwner");
+ Permission> toAdd = Permission.cluster(ClusterPermission.Action.READ);
+ try {
+ // Arrange
+ createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE));
+
+ // Act
+ Result> response = roles.permissionAdder().withRole(myRole)
+ .withPermissions(toAdd)
+ .run();
+ assertNull("add-permissions operation error", response.getError());
+
+ // Assert
+ assertTrue("should have permission " + toAdd, checkHasPermission(myRole, toAdd));
+ } finally {
+ deleteRole(myRole);
+ }
+ }
+
+ @Test
+ public void testRemovePermissions() {
+ String myRole = roleName("VectorOwner");
+ Permission> toRemove = Permission.tenants(TenantsPermission.Action.DELETE);
+ try {
+ // Arrange
+ createRole(myRole,
+ Permission.cluster(ClusterPermission.Action.READ),
+ Permission.tenants(TenantsPermission.Action.DELETE));
+
+ // Act
+ Result> response = roles.permissionRemover().withRole(myRole)
+ .withPermissions(toRemove)
+ .run();
+ assertNull("remove-permissions operation error", response.getError());
+
+ // Assert
+ assertFalse("should not have permission " + toRemove, checkHasPermission(myRole, toRemove));
+ } finally {
+ deleteRole(myRole);
+ }
+ }
+
+ @Test
+ public void testRevokeRole() {
+ String myRole = roleName("VectorOwner");
+ try {
+ // Arrange
+ createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE));
+ roles.assigner().withUser(adminUser).witRoles(myRole).run();
+ assumeTrue(checkHasRole(adminUser, myRole), adminUser + " should have the assigned role");
+
+ // Act
+ Result> response = roles.revoker().withUser(adminUser).witRoles(myRole).run();
+ assertNull("revoke operation error", response.getError());
+
+ // Assert
+ assertFalse("should not have " + myRole + "role", checkHasRole(adminUser, myRole));
+ } finally {
+ deleteRole(myRole);
+ }
+ }
+
+ @Test
+ public void testAssignRole() {
+ String myRole = roleName("VectorOwner");
+ try {
+ // Arrange
+ createRole(myRole, Permission.tenants(TenantsPermission.Action.DELETE));
+ assumeFalse(checkHasRole(adminUser, myRole), adminUser + " should not have the new role");
+
+ // Act
+ Result> response = roles.assigner().withUser(adminUser).witRoles(myRole).run();
+ assertNull("assign operation error", response.getError());
+
+ // Assert
+ assertTrue("should have " + myRole + "role", checkHasRole(adminUser, myRole));
+ } finally {
+ deleteRole(myRole);
+ }
+ }
+
+ /** Prefix the role with the name of the current test for easier debugging */
+ private String roleName(String name) {
+ return String.format("%s-%s", currentTest.getMethodName(), name);
+ }
+
+ private boolean checkHasPermission(String role, Permission extends Permission>> perm) {
+ return roles.permissionChecker().withRole(role).withPermission(perm).run().getResult();
+ }
+
+ private boolean checkRoleExists(String role) {
+ return roles.exists().withName(role).run().getResult();
+ }
+
+ private boolean checkHasRole(String user, String role) {
+ return roles.assignedUsersGetter().withRole(role).run().getResult().contains(user);
+ }
+
+ private void createRole(String role, Permission>... permissions) {
+ roles.creator().withName(role).withPermissions(permissions).run();
+ assumeTrue(checkRoleExists(role), "role should exist after creation");
+
+ }
+
+ private void deleteRole(String role) {
+ roles.deleter().withName(role).run();
+ assertFalse("role should not exist after deletion", checkRoleExists(role));
+
+ }
+}
diff --git a/src/test/java/io/weaviate/integration/client/schema/ClientSchemaTest.java b/src/test/java/io/weaviate/integration/client/schema/ClientSchemaTest.java
index 8506578ba..3ed6b8553 100644
--- a/src/test/java/io/weaviate/integration/client/schema/ClientSchemaTest.java
+++ b/src/test/java/io/weaviate/integration/client/schema/ClientSchemaTest.java
@@ -361,7 +361,7 @@ public void testSchemaCreateClassWithInvalidTokenizationProperty() {
//then
assertResultTrue(createStatus);
- assertResultError("tokenization in body should be one of [word lowercase whitespace field trigram gse kagome_kr]", notExistingTokenizationCreateStatus);
+ assertResultError("tokenization in body should be one of [word lowercase whitespace field trigram gse kagome_kr kagome_ja]", notExistingTokenizationCreateStatus);
assertResultError("Tokenization is not allowed for data type 'int'", notSupportedTokenizationForIntCreateStatus);
}